@vaadin/hilla-file-router 24.5.0-alpha9 → 24.5.0-beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/package.json +6 -5
  2. package/runtime/RouterConfigurationBuilder.d.ts +6 -0
  3. package/runtime/RouterConfigurationBuilder.d.ts.map +1 -1
  4. package/runtime/RouterConfigurationBuilder.js +46 -3
  5. package/runtime/RouterConfigurationBuilder.js.map +2 -2
  6. package/runtime/createMenuItems.d.ts +15 -5
  7. package/runtime/createMenuItems.d.ts.map +1 -1
  8. package/runtime/createMenuItems.js +21 -6
  9. package/runtime/createMenuItems.js.map +2 -2
  10. package/runtime/createRoute.d.ts +3 -2
  11. package/runtime/createRoute.d.ts.map +1 -1
  12. package/runtime/createRoute.js +3 -2
  13. package/runtime/createRoute.js.map +2 -2
  14. package/types.d.ts +7 -0
  15. package/vite-plugin/applyLayouts.d.ts +16 -0
  16. package/vite-plugin/applyLayouts.d.ts.map +1 -0
  17. package/vite-plugin/applyLayouts.js +28 -0
  18. package/vite-plugin/applyLayouts.js.map +7 -0
  19. package/vite-plugin/collectRoutesFromFS.d.ts +1 -0
  20. package/vite-plugin/collectRoutesFromFS.d.ts.map +1 -1
  21. package/vite-plugin/collectRoutesFromFS.js.map +2 -2
  22. package/vite-plugin/createRoutesFromMeta.d.ts.map +1 -1
  23. package/vite-plugin/createRoutesFromMeta.js +5 -4
  24. package/vite-plugin/createRoutesFromMeta.js.map +2 -2
  25. package/vite-plugin/createViewConfigJson.d.ts.map +1 -1
  26. package/vite-plugin/createViewConfigJson.js +6 -1
  27. package/vite-plugin/createViewConfigJson.js.map +2 -2
  28. package/vite-plugin/generateRuntimeFiles.d.ts +4 -0
  29. package/vite-plugin/generateRuntimeFiles.d.ts.map +1 -1
  30. package/vite-plugin/generateRuntimeFiles.js +24 -21
  31. package/vite-plugin/generateRuntimeFiles.js.map +2 -2
  32. package/vite-plugin.d.ts.map +1 -1
  33. package/vite-plugin.js +3 -2
  34. package/vite-plugin.js.map +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/hilla-file-router",
3
- "version": "24.5.0-alpha9",
3
+ "version": "24.5.0-beta2",
4
4
  "description": "Hilla file-based router",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -58,7 +58,7 @@
58
58
  },
59
59
  "peerDependencies": {
60
60
  "react": "^18.2.0",
61
- "react-router-dom": "^6.21.1"
61
+ "react-router-dom": "^6.26.2"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@esm-bundle/chai": "^4.3.4-fix.0",
@@ -79,10 +79,11 @@
79
79
  "type-fest": "^4.9.0"
80
80
  },
81
81
  "dependencies": {
82
- "@vaadin/hilla-generator-utils": "24.5.0-alpha9",
83
- "@vaadin/hilla-react-auth": "24.5.0-alpha9",
82
+ "@vaadin/hilla-generator-utils": "24.5.0-beta2",
83
+ "@vaadin/hilla-react-auth": "24.5.0-beta2",
84
+ "@vaadin/hilla-react-signals": "24.5.0-beta2",
84
85
  "react": "^18.2.0",
85
86
  "rollup": "^4.12.0",
86
- "typescript": "5.5.2"
87
+ "typescript": "5.6.2"
87
88
  }
88
89
  }
@@ -37,6 +37,12 @@ export declare class RouterConfigurationBuilder {
37
37
  * each fallback component.
38
38
  */
39
39
  withFallback(component: ComponentType, config?: ViewConfig): this;
40
+ /**
41
+ * Adds the layoutComponent as the parent layout to views with the flowLayouts ViewConfiguration set.
42
+ *
43
+ * @param layoutComponent - layout component to use, usually Flow
44
+ */
45
+ withLayout(layoutComponent: ComponentType): this;
40
46
  /**
41
47
  * Protects all the routes that require authentication. For more details see
42
48
  * {@link @vaadin/hilla-react-auth#protectRoutes} function.
@@ -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;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;AAQD,KAAK,gBAAgB,CAAC,CAAC,IAAI,CACzB,QAAQ,EAAE,WAAW,GAAG,SAAS,EACjC,UAAU,EAAE,CAAC,GAAG,SAAS,EACzB,QAAQ,CAAC,EAAE,SAAS,WAAW,EAAE,KAC9B,WAAW,GAAG,SAAS,CAAC;AAM7B;;;GAGG;AACH,qBAAa,0BAA0B;;IAGrC;;;;;OAKG;IACH,eAAe,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,IAAI;IAIrD;;;;;;OAMG;IACH,cAAc,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI;IAoCtD;;;;;;;;OAQG;IACH,YAAY,CAAC,SAAS,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI;IAmCjE;;;;;;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;CASzD"}
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;AAQD,KAAK,gBAAgB,CAAC,CAAC,IAAI,CACzB,QAAQ,EAAE,WAAW,GAAG,SAAS,EACjC,UAAU,EAAE,CAAC,GAAG,SAAS,EACzB,QAAQ,CAAC,EAAE,SAAS,WAAW,EAAE,KAC9B,WAAW,GAAG,SAAS,CAAC;AAM7B;;;GAGG;AACH,qBAAa,0BAA0B;;IAGrC;;;;;OAKG;IACH,eAAe,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,IAAI;IAIrD;;;;;;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;IAsCjE;;;;OAIG;IACH,UAAU,CAAC,eAAe,EAAE,aAAa,GAAG,IAAI;IA2ChD;;;;;;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;CASzD"}
@@ -32,14 +32,15 @@ class RouterConfigurationBuilder {
32
32
  withFileRoutes(routes) {
33
33
  return this.update(routes, (original, added, children) => {
34
34
  if (added) {
35
- const { module, path } = added;
35
+ const { module, path, flowLayout } = added;
36
36
  if (!isReactRouteModule(module)) {
37
37
  throw new Error(`The module for the "${path}" section doesn't have the React component exported by default`);
38
38
  }
39
39
  const element = module?.default ? createElement(module.default) : void 0;
40
40
  const handle = {
41
41
  ...module?.config,
42
- title: module?.config?.title ?? convertComponentNameToTitle(module?.default)
42
+ title: module?.config?.title ?? convertComponentNameToTitle(module?.default),
43
+ flowLayout: module?.config?.flowLayout ?? flowLayout
43
44
  };
44
45
  if (path === "" && !children) {
45
46
  return {
@@ -70,12 +71,13 @@ class RouterConfigurationBuilder {
70
71
  * each fallback component.
71
72
  */
72
73
  withFallback(component, config) {
74
+ this.withLayout(component);
73
75
  const fallbackRoutes = [
74
76
  { path: "*", element: createElement(component), handle: config },
75
77
  { index: true, element: createElement(component), handle: config }
76
78
  ];
77
79
  this.update(fallbackRoutes, (original, added, children) => {
78
- if (original) {
80
+ if (original && !original.handle?.ignoreFallback) {
79
81
  if (!children) {
80
82
  return original;
81
83
  }
@@ -95,6 +97,47 @@ class RouterConfigurationBuilder {
95
97
  });
96
98
  return this;
97
99
  }
100
+ /**
101
+ * Adds the layoutComponent as the parent layout to views with the flowLayouts ViewConfiguration set.
102
+ *
103
+ * @param layoutComponent - layout component to use, usually Flow
104
+ */
105
+ withLayout(layoutComponent) {
106
+ function applyLayouts(routes) {
107
+ if (routes.length === 0) {
108
+ return routes;
109
+ }
110
+ const nestedRoutes = routes.map((route) => route);
111
+ return [
112
+ {
113
+ element: createElement(layoutComponent),
114
+ children: nestedRoutes,
115
+ handle: {
116
+ ignoreFallback: true
117
+ }
118
+ }
119
+ ];
120
+ }
121
+ function checkFlowLayout(route) {
122
+ let flowLayout = typeof route.handle === "object" && "flowLayout" in route.handle && route.handle.flowLayout;
123
+ if (!flowLayout && route.children) {
124
+ flowLayout = route.children.filter((child) => checkFlowLayout(child)).length > 0;
125
+ }
126
+ return flowLayout;
127
+ }
128
+ this.#modifiers.push((routes) => {
129
+ if (!routes) {
130
+ return routes;
131
+ }
132
+ const withLayout = routes.filter((route) => checkFlowLayout(route));
133
+ const allRoutes = routes.filter((route) => !withLayout.includes(route));
134
+ const catchAll = [routes.find((route) => route.path === "*")].filter((route) => route !== void 0);
135
+ withLayout.push(...catchAll);
136
+ allRoutes.unshift(...applyLayouts(withLayout));
137
+ return allRoutes;
138
+ });
139
+ return this;
140
+ }
98
141
  /**
99
142
  * Protects all the routes that require authentication. For more details see
100
143
  * {@link @vaadin/hilla-react-auth#protectRoutes} function.
@@ -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 { 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\ntype RoutesModifier = (routes: readonly RouteObject[] | undefined) => readonly RouteObject[] | undefined;\n\nfunction isReactRouteModule(module?: Module): module is RouteModule<ComponentType> | undefined {\n return module ? 'default' in module && typeof module.default === 'function' : true;\n}\n\ntype RouteTransformer<T> = (\n original: RouteObject | undefined,\n overriding: T | undefined,\n children?: readonly RouteObject[],\n) => RouteObject | 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\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: readonly RouteObject[]): 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 } = 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 };\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 // Fallback adds two routes, so that the index (empty path) has a fallback too\n const fallbackRoutes: readonly RouteObject[] = [\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) {\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 * 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<[readonly RouteObject[] | undefined, readonly T[] | undefined], readonly RouteObject[] | 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(Boolean) as readonly RouteObject[];\n } else if (added) {\n return added\n .map((route) => callback(undefined, route, next(undefined, route.children)))\n .filter(Boolean) as readonly RouteObject[];\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 const routes =\n this.#modifiers.reduce<readonly RouteObject[] | 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"],
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\ntype RoutesModifier = (routes: readonly RouteObject[] | undefined) => readonly RouteObject[] | undefined;\n\nfunction isReactRouteModule(module?: Module): module is RouteModule<ComponentType> | undefined {\n return module ? 'default' in module && typeof module.default === 'function' : true;\n}\n\ntype RouteTransformer<T> = (\n original: RouteObject | undefined,\n overriding: T | undefined,\n children?: readonly RouteObject[],\n) => RouteObject | 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\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: readonly RouteObject[]): 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: readonly RouteObject[] = [\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 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n if (original && !original.handle?.ignoreFallback) {\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 function applyLayouts(routes: readonly RouteObject[]): readonly RouteObject[] {\n if (routes.length === 0) {\n return routes;\n }\n const nestedRoutes = routes.map((route) => route);\n return [\n {\n element: createElement(layoutComponent),\n children: nestedRoutes,\n handle: {\n ignoreFallback: true,\n },\n },\n ];\n }\n\n function checkFlowLayout(route: RouteObject): boolean {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n let flowLayout = typeof route.handle === 'object' && 'flowLayout' in route.handle && route.handle.flowLayout;\n // Check children if they have layout. If yes then parent should have layout also.\n if (!flowLayout && route.children) {\n flowLayout = route.children.filter((child) => checkFlowLayout(child)).length > 0;\n }\n return flowLayout;\n }\n\n this.#modifiers.push((routes: readonly RouteObject[] | undefined) => {\n if (!routes) {\n return routes;\n }\n const withLayout = routes.filter((route) => checkFlowLayout(route));\n const allRoutes = routes.filter((route) => !withLayout.includes(route));\n const catchAll = [routes.find((route) => route.path === '*')].filter((route) => route !== undefined);\n withLayout.push(...catchAll); // Add * fallback to all child routes\n\n allRoutes.unshift(...applyLayouts(withLayout));\n return allRoutes;\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<[readonly RouteObject[] | undefined, readonly T[] | undefined], readonly RouteObject[] | 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(Boolean) as readonly RouteObject[];\n } else if (added) {\n return added\n .map((route) => callback(undefined, route, next(undefined, route.children)))\n .filter(Boolean) as readonly RouteObject[];\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 const routes =\n this.#modifiers.reduce<readonly RouteObject[] | 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"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,14 +1,24 @@
1
- import type { MenuItem, ViewConfig } from '../types.js';
1
+ import type { MenuItem } from '../types.js';
2
+ export declare const viewsSignal: import("@preact/signals-core").Signal<Readonly<Record<string, Readonly<{
3
+ title?: string;
4
+ rolesAllowed?: readonly [string, ...string[]];
5
+ loginRequired?: boolean;
6
+ route?: string;
7
+ flowLayout?: boolean;
8
+ menu?: Readonly<{
9
+ title?: string;
10
+ order?: number;
11
+ exclude?: boolean;
12
+ icon?: string;
13
+ }>;
14
+ }>>> | undefined>;
2
15
  /**
3
16
  * Creates menu items from the views provided by the server. The views are sorted according to the
4
17
  * {@link ViewConfig.menu.order}, filtered out if they are explicitly excluded via {@link ViewConfig.menu.exclude}.
5
18
  * Note that views with no order are put below views with an order. Ties are resolved based on the path string
6
19
  * comparison.
7
20
  *
8
- * @param vaadinObject - The Vaadin object containing the server views.
9
21
  * @returns A list of menu items.
10
22
  */
11
- export declare function createMenuItems(vaadinObject?: Readonly<{
12
- views: Readonly<Record<string, ViewConfig>>;
13
- }> | undefined): readonly MenuItem[];
23
+ export declare function createMenuItems(): readonly MenuItem[];
14
24
  //# sourceMappingURL=createMenuItems.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"createMenuItems.d.ts","sourceRoot":"","sources":["../src/runtime/createMenuItems.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAExD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,YAAY;;cAAkC,GAAG,SAAS,QAAQ,EAAE,CAsBnG"}
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;;;;;;;aAsBU,CAAC;aAMJ,CAAC;eAIxB,CAAD;YAAmE,CAAC;;iBAhCF,CAAC;AAE1E;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,SAAS,QAAQ,EAAE,CA0BrD"}
@@ -2,13 +2,18 @@ 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.5.0-alpha9"
5
+ version: "24.5.0-beta2"
6
6
  });
7
7
  }
8
- function createMenuItems(vaadinObject = window.Vaadin) {
9
- __REGISTER__("createMenuItems", vaadinObject);
8
+ import { signal } from "@vaadin/hilla-react-signals";
9
+ const viewsSignal = signal(window.Vaadin?.views);
10
+ function createMenuItems() {
11
+ __REGISTER__("createMenuItems", window.Vaadin?.views);
10
12
  const collator = new Intl.Collator("en-US");
11
- return vaadinObject?.views ? Object.entries(vaadinObject.views).filter(([_, value]) => !value.menu?.exclude).map(([path, config]) => ({
13
+ if (!viewsSignal.value) {
14
+ return [];
15
+ }
16
+ return Object.entries(viewsSignal.value).filter(([_, value]) => !value.menu?.exclude).map(([path, config]) => ({
12
17
  to: path,
13
18
  icon: config.menu?.icon,
14
19
  title: config.menu?.title ?? config.title,
@@ -16,9 +21,19 @@ function createMenuItems(vaadinObject = window.Vaadin) {
16
21
  })).sort((menuA, menuB) => {
17
22
  const ordersDiff = (menuA.order ?? Number.MAX_VALUE) - (menuB.order ?? Number.MAX_VALUE);
18
23
  return ordersDiff !== 0 ? ordersDiff : collator.compare(menuA.to, menuB.to);
19
- }) : [];
24
+ });
25
+ }
26
+ if (import.meta.hot) {
27
+ import.meta.hot.on("fs-route-update", () => {
28
+ fetch("?v-r=routeinfo").then(async (resp) => resp.json()).then((json) => {
29
+ viewsSignal.value = json;
30
+ }).catch((e) => {
31
+ console.error("Failed to fetch route info", e);
32
+ });
33
+ });
20
34
  }
21
35
  export {
22
- createMenuItems
36
+ createMenuItems,
37
+ viewsSignal
23
38
  };
24
39
  //# sourceMappingURL=createMenuItems.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../scripts/register.js", "../src/runtime/createMenuItems.ts"],
4
- "sourcesContent": ["export function __REGISTER__(feature, vaadinObj = (window.Vaadin ??= {})) {\n vaadinObj.registrations ??= [];\n vaadinObj.registrations.push({\n is: feature ? `${__NAME__}/${feature}` : __NAME__,\n version: __VERSION__,\n });\n}\n", "import type { VaadinWindow } from '../shared/internal.js';\nimport type { MenuItem, ViewConfig } from '../types.js';\n\n/**\n * Creates menu items from the views provided by the server. The views are sorted according to the\n * {@link ViewConfig.menu.order}, filtered out if they are explicitly excluded via {@link ViewConfig.menu.exclude}.\n * Note that views with no order are put below views with an order. Ties are resolved based on the path string\n * comparison.\n *\n * @param vaadinObject - The Vaadin object containing the server views.\n * @returns A list of menu items.\n */\nexport function createMenuItems(vaadinObject = (window as VaadinWindow).Vaadin): readonly MenuItem[] {\n // @ts-expect-error: esbuild injection\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n __REGISTER__('createMenuItems', vaadinObject);\n const collator = new Intl.Collator('en-US');\n return vaadinObject?.views\n ? Object.entries(vaadinObject.views)\n // Filter out the views that are explicitly excluded from the menu.\n .filter(([_, value]) => !value.menu?.exclude)\n // Map the views to menu items.\n .map(([path, config]) => ({\n to: path,\n icon: config.menu?.icon,\n title: config.menu?.title ?? config.title,\n order: config.menu?.order,\n }))\n // Sort views according to the order specified in the view configuration.\n .sort((menuA, menuB) => {\n const ordersDiff = (menuA.order ?? Number.MAX_VALUE) - (menuB.order ?? Number.MAX_VALUE);\n return ordersDiff !== 0 ? ordersDiff : collator.compare(menuA.to, menuB.to);\n })\n : [];\n}\n"],
5
- "mappings": "AAAO,SAAS,aAAa,SAAS,YAAa,OAAO,WAAW,CAAC,GAAI;AACxE,YAAU,kBAAkB,CAAC;AAC7B,YAAU,cAAc,KAAK;AAAA,IAC3B,IAAI,UAAU,GAAG,2BAAQ,IAAI,OAAO,KAAK;AAAA,IACzC,SAAS;AAAA,EACX,CAAC;AACH;ACMO,SAAS,gBAAgB,eAAgB,OAAwB,QAA6B;AAGnG,eAAa,mBAAmB,YAAY;AAC5C,QAAM,WAAW,IAAI,KAAK,SAAS,OAAO;AAC1C,SAAO,cAAc,QACjB,OAAO,QAAQ,aAAa,KAAK,EAE9B,OAAO,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,MAAM,MAAM,OAAO,EAE3C,IAAI,CAAC,CAAC,MAAM,MAAM,OAAO;AAAA,IACxB,IAAI;AAAA,IACJ,MAAM,OAAO,MAAM;AAAA,IACnB,OAAO,OAAO,MAAM,SAAS,OAAO;AAAA,IACpC,OAAO,OAAO,MAAM;AAAA,EACtB,EAAE,EAED,KAAK,CAAC,OAAO,UAAU;AACtB,UAAM,cAAc,MAAM,SAAS,OAAO,cAAc,MAAM,SAAS,OAAO;AAC9E,WAAO,eAAe,IAAI,aAAa,SAAS,QAAQ,MAAM,IAAI,MAAM,EAAE;AAAA,EAC5E,CAAC,IACH,CAAC;AACP;",
4
+ "sourcesContent": ["export function __REGISTER__(feature, vaadinObj = (window.Vaadin ??= {})) {\n vaadinObj.registrations ??= [];\n vaadinObj.registrations.push({\n is: feature ? `${__NAME__}/${feature}` : __NAME__,\n version: __VERSION__,\n });\n}\n", "import { signal } from '@vaadin/hilla-react-signals';\nimport type { VaadinWindow } from '../shared/internal.js';\nimport type { MenuItem, ViewConfig } from '../types.js';\n\nexport const viewsSignal = signal((window as VaadinWindow).Vaadin?.views);\n\n/**\n * Creates menu items from the views provided by the server. The views are sorted according to the\n * {@link ViewConfig.menu.order}, filtered out if they are explicitly excluded via {@link ViewConfig.menu.exclude}.\n * Note that views with no order are put below views with an order. Ties are resolved based on the path string\n * comparison.\n *\n * @returns A list of menu items.\n */\nexport function createMenuItems(): readonly MenuItem[] {\n // @ts-expect-error: esbuild injection\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n __REGISTER__('createMenuItems', (window as VaadinWindow).Vaadin?.views);\n const collator = new Intl.Collator('en-US');\n if (!viewsSignal.value) {\n return [];\n }\n\n return (\n Object.entries(viewsSignal.value)\n // Filter out the views that are explicitly excluded from the menu.\n .filter(([_, value]) => !value.menu?.exclude)\n // Map the views to menu items.\n .map(([path, config]) => ({\n to: path,\n icon: config.menu?.icon,\n title: config.menu?.title ?? config.title,\n order: config.menu?.order,\n }))\n // Sort views according to the order specified in the view configuration.\n .sort((menuA, menuB) => {\n const ordersDiff = (menuA.order ?? Number.MAX_VALUE) - (menuB.order ?? Number.MAX_VALUE);\n return ordersDiff !== 0 ? ordersDiff : collator.compare(menuA.to, menuB.to);\n })\n );\n}\n\n// @ts-expect-error Vite hotswapping\nif (import.meta.hot) {\n // @ts-expect-error Vite hotswapping\n // eslint-disable-next-line\n import.meta.hot.on('fs-route-update', () => {\n fetch('?v-r=routeinfo')\n .then(async (resp) => resp.json())\n .then((json) => {\n viewsSignal.value = json;\n })\n .catch((e) => {\n console.error('Failed to fetch route info', e);\n });\n });\n}\n"],
5
+ "mappings": "AAAO,SAAS,aAAa,SAAS,YAAa,OAAO,WAAW,CAAC,GAAI;AACxE,YAAU,kBAAkB,CAAC;AAC7B,YAAU,cAAc,KAAK;AAAA,IAC3B,IAAI,UAAU,GAAG,2BAAQ,IAAI,OAAO,KAAK;AAAA,IACzC,SAAS;AAAA,EACX,CAAC;AACH;ACNA,SAAS,cAAc;AAIhB,MAAM,cAAc,OAAQ,OAAwB,QAAQ,KAAK;AAUjE,SAAS,kBAAuC;AAGrD,eAAa,mBAAoB,OAAwB,QAAQ,KAAK;AACtE,QAAM,WAAW,IAAI,KAAK,SAAS,OAAO;AAC1C,MAAI,CAAC,YAAY,OAAO;AACtB,WAAO,CAAC;AAAA,EACV;AAEA,SACE,OAAO,QAAQ,YAAY,KAAK,EAE7B,OAAO,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,MAAM,MAAM,OAAO,EAE3C,IAAI,CAAC,CAAC,MAAM,MAAM,OAAO;AAAA,IACxB,IAAI;AAAA,IACJ,MAAM,OAAO,MAAM;AAAA,IACnB,OAAO,OAAO,MAAM,SAAS,OAAO;AAAA,IACpC,OAAO,OAAO,MAAM;AAAA,EACtB,EAAE,EAED,KAAK,CAAC,OAAO,UAAU;AACtB,UAAM,cAAc,MAAM,SAAS,OAAO,cAAc,MAAM,SAAS,OAAO;AAC9E,WAAO,eAAe,IAAI,aAAa,SAAS,QAAQ,MAAM,IAAI,MAAM,EAAE;AAAA,EAC5E,CAAC;AAEP;AAGA,IAAI,YAAY,KAAK;AAGnB,cAAY,IAAI,GAAG,mBAAmB,MAAM;AAC1C,UAAM,gBAAgB,EACnB,KAAK,OAAO,SAAS,KAAK,KAAK,CAAC,EAChC,KAAK,CAAC,SAAS;AACd,kBAAY,QAAQ;AAAA,IACtB,CAAC,EACA,MAAM,CAAC,MAAM;AACZ,cAAQ,MAAM,8BAA8B,CAAC;AAAA,IAC/C,CAAC;AAAA,EACL,CAAC;AACH;",
6
6
  "names": []
7
7
  }
@@ -8,6 +8,7 @@ import type { AgnosticRoute, Module } from '../types.js';
8
8
  *
9
9
  * @returns A framework-agnostic route object.
10
10
  */
11
- export declare function createRoute(path: string, children?: readonly AgnosticRoute[]): AgnosticRoute;
12
- export declare function createRoute(path: string, module: Module, children?: readonly AgnosticRoute[]): AgnosticRoute;
11
+ export declare function createRoute(path: string, flowLayout: boolean, children?: readonly AgnosticRoute[]): AgnosticRoute;
12
+ export declare function createRoute(path: string, flowLayout: boolean, module: Module, children?: readonly AgnosticRoute[]): AgnosticRoute;
13
+ export declare function createRoute(path: string, flowLayout: boolean, module: Module, children?: readonly AgnosticRoute[]): AgnosticRoute;
13
14
  //# sourceMappingURL=createRoute.d.ts.map
@@ -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,MAAM,aAAa,CAAC;AAEzD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,CAAC;AACnH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,OAAO,EACnB,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,GAClC,aAAa,CAAC;AACjB,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,OAAO,EACnB,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,GAClC,aAAa,CAAC"}
@@ -1,4 +1,4 @@
1
- function createRoute(path, moduleOrChildren, children) {
1
+ function createRoute(path, flowLayout, moduleOrChildren, children) {
2
2
  let module;
3
3
  if (Array.isArray(moduleOrChildren)) {
4
4
  children = moduleOrChildren;
@@ -8,7 +8,8 @@ function createRoute(path, moduleOrChildren, children) {
8
8
  return {
9
9
  path,
10
10
  module,
11
- children
11
+ children,
12
+ flowLayout
12
13
  };
13
14
  }
14
15
  export {
@@ -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 } 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, flowLayout: boolean, children?: readonly AgnosticRoute[]): AgnosticRoute;\nexport function createRoute(\n path: string,\n flowLayout: boolean,\n module: Module,\n children?: readonly AgnosticRoute[],\n): AgnosticRoute;\nexport function createRoute(\n path: string,\n flowLayout: boolean,\n module: Module,\n children?: readonly AgnosticRoute[],\n): AgnosticRoute;\nexport function createRoute(\n path: string,\n flowLayout: boolean,\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 flowLayout,\n };\n}\n"],
5
+ "mappings": "AAwBO,SAAS,YACd,MACA,YACA,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,IACA;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
package/types.d.ts CHANGED
@@ -25,6 +25,12 @@ export type ViewConfig = Readonly<{
25
25
  */
26
26
  route?: string;
27
27
 
28
+ /**
29
+ * Set to true to indicate that the view is using server side parent layout
30
+ * annotated with the Layout annotation.
31
+ */
32
+ flowLayout?: boolean;
33
+
28
34
  menu?: Readonly<{
29
35
  /**
30
36
  * Title to use in the menu. Falls back the title property of the view
@@ -71,6 +77,7 @@ export type AgnosticRoute = Readonly<{
71
77
  path: string;
72
78
  module?: Module;
73
79
  children?: readonly AgnosticRoute[];
80
+ flowLayout?: boolean;
74
81
  }>;
75
82
 
76
83
  /**
@@ -0,0 +1,16 @@
1
+ import type { RouteMeta } from './collectRoutesFromFS.js';
2
+ /**
3
+ * The information about a particular server-side layout.
4
+ */
5
+ export type LayoutMeta = Readonly<{
6
+ path: string;
7
+ }>;
8
+ /**
9
+ * Enables Flow layout flag on the matching routes based on the information from the layouts JSON file.
10
+ *
11
+ * @param routeMeta - The routes tree to process.
12
+ * @param layoutsFile - The server layouts JSON file.
13
+ * @returns Processed routes tree.
14
+ */
15
+ export default function applyLayouts(routeMeta: readonly RouteMeta[], layoutsFile: URL): Promise<readonly RouteMeta[]>;
16
+ //# sourceMappingURL=applyLayouts.d.ts.map
@@ -0,0 +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;AAmBH;;;;;;GAMG;AACH,wBAA8B,YAAY,CACxC,SAAS,EAAE,SAAS,SAAS,EAAE,EAC/B,WAAW,EAAE,GAAG,GACf,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAS/B"}
@@ -0,0 +1,28 @@
1
+ import { existsSync } from "node:fs";
2
+ 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
+ async function applyLayouts(routeMeta, layoutsFile) {
17
+ if (!existsSync(layoutsFile)) {
18
+ return routeMeta;
19
+ }
20
+ const layoutContents = await readFile(layoutsFile, "utf-8");
21
+ const availableLayouts = JSON.parse(layoutContents);
22
+ const layoutPaths = new Set(availableLayouts.map((layout) => stripLeadingSlash(layout.path)));
23
+ return routeMeta.map((route) => layoutPaths.has(stripLeadingSlash(route.path)) ? enableFlowLayout(route) : route);
24
+ }
25
+ export {
26
+ applyLayouts as default
27
+ };
28
+ //# sourceMappingURL=applyLayouts.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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 * 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 = new Set(availableLayouts.map((layout) => stripLeadingSlash(layout.path)));\n\n return routeMeta.map((route) => (layoutPaths.has(stripLeadingSlash(route.path)) ? enableFlowLayout(route) : route));\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;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,IAAI,IAAI,iBAAiB,IAAI,CAAC,WAAW,kBAAkB,OAAO,IAAI,CAAC,CAAC;AAE5F,SAAO,UAAU,IAAI,CAAC,UAAW,YAAY,IAAI,kBAAkB,MAAM,IAAI,CAAC,IAAI,iBAAiB,KAAK,IAAI,KAAM;AACpH;",
6
+ "names": []
7
+ }
@@ -3,6 +3,7 @@ export type RouteMeta = Readonly<{
3
3
  path: string;
4
4
  file?: URL;
5
5
  layout?: URL;
6
+ flowLayout?: boolean;
6
7
  children?: readonly RouteMeta[];
7
8
  }>;
8
9
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"collectRoutesFromFS.d.ts","sourceRoot":"","sources":["../src/vite-plugin/collectRoutesFromFS.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,QAAQ,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;CACjC,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C;;OAEG;IACH,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,GAAG,CAAC;IACb;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAoBH;;;;;;;;;;;;;GAaG;AACH,wBAA8B,mBAAmB,CAC/C,GAAG,EAAE,GAAG,EACR,EAAE,UAAU,EAAE,MAAM,EAAE,MAAY,EAAE,EAAE,oBAAoB,GACzD,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAuF/B"}
1
+ {"version":3,"file":"collectRoutesFromFS.d.ts","sourceRoot":"","sources":["../src/vite-plugin/collectRoutesFromFS.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;CACjC,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C;;OAEG;IACH,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,GAAG,CAAC;IACb;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAoBH;;;;;;;;;;;;;GAaG;AACH,wBAA8B,mBAAmB,CAC/C,GAAG,EAAE,GAAG,EACR,EAAE,UAAU,EAAE,MAAM,EAAE,MAAY,EAAE,EAAE,oBAAoB,GACzD,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAuF/B"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/vite-plugin/collectRoutesFromFS.ts"],
4
- "sourcesContent": ["import { opendir, readFile } from 'node:fs/promises';\nimport { basename, extname, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Logger } from 'vite';\nimport { RouteParamType } from '../shared/routeParamType.js';\nimport { cleanUp, routeParamTypeMap } from './utils.js';\n\nexport type RouteMeta = Readonly<{\n path: string;\n file?: URL;\n layout?: URL;\n children?: readonly RouteMeta[];\n}>;\n\n/**\n * Routes collector options.\n */\nexport type CollectRoutesOptions = Readonly<{\n /**\n * The list of extensions for files that will be collected as routes.\n */\n extensions: readonly string[];\n /**\n * The parent directory of the current directory. This is a\n * nested parameter used inside the function only.\n */\n parent?: URL;\n /**\n * The Vite logger instance.\n */\n logger: Logger;\n}>;\n\nasync function checkFile(url: URL | undefined, logger: Logger): Promise<URL | undefined> {\n if (url) {\n const contents = await readFile(url, 'utf-8');\n if (contents.trim() === '') {\n return undefined;\n } else if (!contents.includes('export default')) {\n logger.error(`The file \"${String(url)}\" should contain a default export of a component`);\n return undefined;\n }\n }\n\n return url;\n}\n\nconst collator = new Intl.Collator('en-US');\n\nconst warningFor = ['.ts', '.js'];\n\n/**\n * Collect route metadata from the file system and build a route tree.\n *\n * It accepts files that start with `@` as special files.\n * - `@layout` contains a component that wraps the child components.\n * - `@index` contains a component that will be used as the index page of the directory.\n *\n * It accepts files that start with `_` as private files. They will be ignored.\n *\n * @param dir - The directory to collect routes from.\n * @param options - The options object.\n *\n * @returns The route metadata array.\n */\nexport default async function collectRoutesFromFS(\n dir: URL,\n { extensions, logger, parent = dir }: CollectRoutesOptions,\n): Promise<readonly RouteMeta[]> {\n const path = relative(fileURLToPath(parent), fileURLToPath(dir));\n let children: RouteMeta[] = [];\n let layout: URL | undefined;\n\n for await (const d of await opendir(dir)) {\n if (d.name.startsWith('_')) {\n continue;\n }\n\n const extension = extname(d.name);\n const name = basename(d.name, extension);\n\n if (extension !== '' && !extensions.includes(extension)) {\n if (warningFor.includes(extension)) {\n logger.warn(\n `File System based router expects only JSX files in 'Frontend/views/' directory, such as '*.tsx' and '*.jsx'. The file '${d.name}' will be ignored by router, as it doesn't match this convention. Please consider storing it in another directory.`,\n );\n }\n continue;\n }\n\n if (children.some(({ path: p }) => p === name)) {\n throw new Error(`You cannot create a file and a directory with the same name (\"${name}\"). Use \"@index\" instead`);\n }\n\n if (d.isDirectory()) {\n const directoryRoutes = await collectRoutesFromFS(new URL(`${name}/`, dir), {\n extensions,\n logger,\n parent: dir,\n });\n if (directoryRoutes.length === 1 && directoryRoutes[0].layout) {\n const [layoutRoute] = directoryRoutes;\n children.push(layoutRoute);\n } else if (directoryRoutes.length > 0) {\n children.push({ path: name, children: directoryRoutes });\n }\n continue;\n }\n\n const file = new URL(d.name, dir);\n const url = await checkFile(file, logger);\n if (url === undefined) {\n continue;\n }\n const optionalParamType = routeParamTypeMap.get(RouteParamType.Optional)!;\n\n if (\n (name === '@index' && children.some(({ path: p }) => p.search(optionalParamType) >= 0)) ||\n (name.search(optionalParamType) >= 0 && children.some(({ path: p }) => p === ''))\n ) {\n throw new Error('You cannot create an `@index` file in a directory with optional parameters');\n } else if (name === '@layout') {\n layout = file;\n } else if (name === '@index') {\n children.push({\n path: '',\n file,\n });\n } else if (name.startsWith('@')) {\n throw new Error(\n 'Symbol \"@\" is reserved for special directories and files; only \"@layout\" and \"@index\" are allowed',\n );\n } else {\n children.push({\n path: name,\n file,\n });\n }\n }\n\n [children, layout] = await Promise.all([\n Promise.all(\n children.map(async (child) => ({\n ...child,\n file: child.file,\n layout: await checkFile(child.layout, logger),\n })),\n ),\n checkFile(layout, logger),\n ]);\n\n children = children.sort(({ path: a }, { path: b }) => collator.compare(cleanUp(a), cleanUp(b)));\n\n // If a layout was found, wrap the other routes with the layout route.\n return layout ? [{ path, layout, children }] : children;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { opendir, readFile } from 'node:fs/promises';\nimport { basename, extname, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Logger } from 'vite';\nimport { RouteParamType } from '../shared/routeParamType.js';\nimport { cleanUp, routeParamTypeMap } from './utils.js';\n\nexport type RouteMeta = Readonly<{\n path: string;\n file?: URL;\n layout?: URL;\n flowLayout?: boolean;\n children?: readonly RouteMeta[];\n}>;\n\n/**\n * Routes collector options.\n */\nexport type CollectRoutesOptions = Readonly<{\n /**\n * The list of extensions for files that will be collected as routes.\n */\n extensions: readonly string[];\n /**\n * The parent directory of the current directory. This is a\n * nested parameter used inside the function only.\n */\n parent?: URL;\n /**\n * The Vite logger instance.\n */\n logger: Logger;\n}>;\n\nasync function checkFile(url: URL | undefined, logger: Logger): Promise<URL | undefined> {\n if (url) {\n const contents = await readFile(url, 'utf-8');\n if (contents.trim() === '') {\n return undefined;\n } else if (!contents.includes('export default')) {\n logger.error(`The file \"${String(url)}\" should contain a default export of a component`);\n return undefined;\n }\n }\n\n return url;\n}\n\nconst collator = new Intl.Collator('en-US');\n\nconst warningFor = ['.ts', '.js'];\n\n/**\n * Collect route metadata from the file system and build a route tree.\n *\n * It accepts files that start with `@` as special files.\n * - `@layout` contains a component that wraps the child components.\n * - `@index` contains a component that will be used as the index page of the directory.\n *\n * It accepts files that start with `_` as private files. They will be ignored.\n *\n * @param dir - The directory to collect routes from.\n * @param options - The options object.\n *\n * @returns The route metadata array.\n */\nexport default async function collectRoutesFromFS(\n dir: URL,\n { extensions, logger, parent = dir }: CollectRoutesOptions,\n): Promise<readonly RouteMeta[]> {\n const path = relative(fileURLToPath(parent), fileURLToPath(dir));\n let children: RouteMeta[] = [];\n let layout: URL | undefined;\n\n for await (const d of await opendir(dir)) {\n if (d.name.startsWith('_')) {\n continue;\n }\n\n const extension = extname(d.name);\n const name = basename(d.name, extension);\n\n if (extension !== '' && !extensions.includes(extension)) {\n if (warningFor.includes(extension)) {\n logger.warn(\n `File System based router expects only JSX files in 'Frontend/views/' directory, such as '*.tsx' and '*.jsx'. The file '${d.name}' will be ignored by router, as it doesn't match this convention. Please consider storing it in another directory.`,\n );\n }\n continue;\n }\n\n if (children.some(({ path: p }) => p === name)) {\n throw new Error(`You cannot create a file and a directory with the same name (\"${name}\"). Use \"@index\" instead`);\n }\n\n if (d.isDirectory()) {\n const directoryRoutes = await collectRoutesFromFS(new URL(`${name}/`, dir), {\n extensions,\n logger,\n parent: dir,\n });\n if (directoryRoutes.length === 1 && directoryRoutes[0].layout) {\n const [layoutRoute] = directoryRoutes;\n children.push(layoutRoute);\n } else if (directoryRoutes.length > 0) {\n children.push({ path: name, children: directoryRoutes });\n }\n continue;\n }\n\n const file = new URL(d.name, dir);\n const url = await checkFile(file, logger);\n if (url === undefined) {\n continue;\n }\n const optionalParamType = routeParamTypeMap.get(RouteParamType.Optional)!;\n\n if (\n (name === '@index' && children.some(({ path: p }) => p.search(optionalParamType) >= 0)) ||\n (name.search(optionalParamType) >= 0 && children.some(({ path: p }) => p === ''))\n ) {\n throw new Error('You cannot create an `@index` file in a directory with optional parameters');\n } else if (name === '@layout') {\n layout = file;\n } else if (name === '@index') {\n children.push({\n path: '',\n file,\n });\n } else if (name.startsWith('@')) {\n throw new Error(\n 'Symbol \"@\" is reserved for special directories and files; only \"@layout\" and \"@index\" are allowed',\n );\n } else {\n children.push({\n path: name,\n file,\n });\n }\n }\n\n [children, layout] = await Promise.all([\n Promise.all(\n children.map(async (child) => ({\n ...child,\n file: child.file,\n layout: await checkFile(child.layout, logger),\n })),\n ),\n checkFile(layout, logger),\n ]);\n\n children = children.sort(({ path: a }, { path: b }) => collator.compare(cleanUp(a), cleanUp(b)));\n\n // If a layout was found, wrap the other routes with the layout route.\n return layout ? [{ path, layout, children }] : children;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"createRoutesFromMeta.d.ts","sourceRoot":"","sources":["../src/vite-plugin/createRoutesFromMeta.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;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":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AA2DjE;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,eAAe,GAAG,MAAM,CA0ErH"}
@@ -19,9 +19,10 @@ function createImport(mod, file) {
19
19
  return template(`import * as ${mod} from '${path}';
20
20
  `, ([statement]) => statement);
21
21
  }
22
- function createRouteData(path, mod, children) {
22
+ function createRouteData(path, flowLayout, mod, children) {
23
+ const serverLayout = flowLayout ?? false;
23
24
  return template(
24
- `const route = createRoute("${path}"${mod ? `, ${mod}` : ""}${children ? `, CHILDREN` : ""})`,
25
+ `const route = createRoute("${path}",${serverLayout}${mod ? `, ${mod}` : ""}${children ? `, CHILDREN` : ""})`,
25
26
  ([statement]) => statement.declarationList.declarations[0].initializer,
26
27
  [
27
28
  transformer(
@@ -39,7 +40,7 @@ function createRoutesFromMeta(views, { code: codeFile }) {
39
40
  errors.push(
40
41
  ...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
  );
42
- return metas.map(({ file: file2, layout, path, children }) => {
43
+ return metas.map(({ file: file2, layout, path, children, flowLayout }) => {
43
44
  let _children;
44
45
  if (children) {
45
46
  _children = next(...children);
@@ -54,7 +55,7 @@ function createRoutesFromMeta(views, { code: codeFile }) {
54
55
  mod = `Layout${currentId}`;
55
56
  imports.push(createImport(mod, relativize(layout, codeDir)));
56
57
  }
57
- return createRouteData(convertFSRouteSegmentToURLPatternFormat(path), mod, _children);
58
+ return createRouteData(convertFSRouteSegmentToURLPatternFormat(path), flowLayout, mod, _children);
58
59
  });
59
60
  });
60
61
  if (imports.length > 0) {
@@ -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 }) => {\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 { 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(\n path: string,\n flowLayout: boolean | undefined,\n mod: string | undefined,\n children?: readonly CallExpression[],\n): CallExpression {\n const serverLayout = flowLayout ?? false;\n return template(\n `const route = createRoute(\"${path}\",${serverLayout}${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), flowLayout, 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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",
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,CAmE/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,CAyE/F"}
@@ -14,7 +14,7 @@ async function createViewConfigJson(views) {
14
14
  const res = await transformTree(
15
15
  views,
16
16
  async (routes, next) => await Promise.all(
17
- routes.map(async ({ path, file, layout, children }) => {
17
+ routes.map(async ({ path, file, layout, children, flowLayout }) => {
18
18
  const newChildren = children ? await next(...children) : void 0;
19
19
  if (!file && !layout) {
20
20
  return {
@@ -38,6 +38,11 @@ async function createViewConfigJson(views) {
38
38
  const code = node.initializer.getText(sourceFile);
39
39
  const script = new Script(`(${code})`);
40
40
  config = script.runInThisContext();
41
+ if (config.flowLayout === void 0) {
42
+ const copy = JSON.parse(JSON.stringify(config));
43
+ copy.flowLayout = flowLayout ?? false;
44
+ config = copy;
45
+ }
41
46
  }
42
47
  } else if (node.getText(sourceFile).startsWith("export default")) {
43
48
  waitingForIdentifier = true;
@@ -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 }) => {\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 }\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 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,SAAS,MAAM;AACrD,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;AAAA,YACnC;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;AAEJ,YAAI,QAAQ,OAAO;AACjB,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,QAAQ,SAAS,IAAI;AAAA,UAC9D;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 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 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;AAEJ,YAAI,QAAQ,OAAO;AACjB,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,QAAQ,SAAS,IAAI;AAAA,UAC9D;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
  }
@@ -12,6 +12,10 @@ export type RuntimeFileUrls = Readonly<{
12
12
  * The URL of the module with the routes tree in a framework-agnostic format.
13
13
  */
14
14
  code: URL;
15
+ /**
16
+ * The URL of the JSON file containing server layout path information.
17
+ */
18
+ layouts: URL;
15
19
  }>;
16
20
  /**
17
21
  * Collects all file-based routes from the given directory, and based on them generates two files
@@ -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;AAKnC;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC;;;OAGG;IACH,IAAI,EAAE,GAAG,CAAC;IACV;;OAEG;IACH,IAAI,EAAE,GAAG,CAAC;CACX,CAAC,CAAC;AAyBH;;;;;;;;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":"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,41 +1,44 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import applyLayouts from "./applyLayouts.js";
3
4
  import collectRoutesFromFS from "./collectRoutesFromFS.js";
4
5
  import createRoutesFromMeta from "./createRoutesFromMeta.js";
5
6
  import createViewConfigJson from "./createViewConfigJson.js";
6
- async function generateRuntimeFile(url, data) {
7
+ async function generateRuntimeFile(url, data, forceWrite) {
7
8
  await mkdir(new URL("./", url), { recursive: true });
8
- let contents;
9
- try {
10
- contents = await readFile(url, "utf-8");
11
- } catch (e) {
12
- if (!(e != null && typeof e === "object" && "code" in e && e.code === "ENOENT")) {
13
- throw e;
9
+ let shouldWrite = forceWrite ?? false;
10
+ if (!forceWrite) {
11
+ let contents;
12
+ try {
13
+ contents = await readFile(url, "utf-8");
14
+ } catch (e) {
15
+ if (!(e != null && typeof e === "object" && "code" in e && e.code === "ENOENT")) {
16
+ throw e;
17
+ }
14
18
  }
19
+ shouldWrite = contents !== data;
15
20
  }
16
- if (contents !== data) {
21
+ if (shouldWrite) {
17
22
  await writeFile(url, data, "utf-8");
18
23
  }
24
+ return shouldWrite;
19
25
  }
20
26
  async function generateRuntimeFiles(viewsDir, urls, extensions, logger, debug) {
21
- const routeMeta = existsSync(viewsDir) ? await collectRoutesFromFS(viewsDir, { extensions, logger }) : [];
27
+ let routeMeta = existsSync(viewsDir) ? await collectRoutesFromFS(viewsDir, { extensions, logger }) : [];
22
28
  if (debug) {
23
29
  logger.info("Collected file-based routes");
24
30
  }
31
+ routeMeta = await applyLayouts(routeMeta, urls.layouts);
25
32
  const runtimeRoutesCode = createRoutesFromMeta(routeMeta, urls);
26
33
  const viewConfigJson = await createViewConfigJson(routeMeta);
27
- await Promise.all([
28
- generateRuntimeFile(urls.json, viewConfigJson).then(() => {
29
- if (debug) {
30
- logger.info(`Frontend route list is generated: ${String(urls.json)}`);
31
- }
32
- }),
33
- generateRuntimeFile(urls.code, runtimeRoutesCode).then(() => {
34
- if (debug) {
35
- logger.info(`File Route module is generated: ${String(urls.code)}`);
36
- }
37
- })
38
- ]);
34
+ const jsonWritten = await generateRuntimeFile(urls.json, viewConfigJson);
35
+ if (debug) {
36
+ logger.info(`Frontend route list is generated: ${String(urls.json)}`);
37
+ }
38
+ await generateRuntimeFile(urls.code, runtimeRoutesCode, jsonWritten);
39
+ if (debug) {
40
+ logger.info(`File Route module is generated: ${String(urls.code)}`);
41
+ }
39
42
  }
40
43
  export {
41
44
  generateRuntimeFiles
@@ -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 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\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 */\nasync function generateRuntimeFile(url: URL, data: string): Promise<void> {\n await mkdir(new URL('./', url), { recursive: true });\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 if (contents !== data) {\n await writeFile(url, data, 'utf-8');\n }\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 const routeMeta = existsSync(viewsDir) ? await collectRoutesFromFS(viewsDir, { extensions, logger }) : [];\n if (debug) {\n logger.info('Collected file-based routes');\n }\n const runtimeRoutesCode = createRoutesFromMeta(routeMeta, urls);\n const viewConfigJson = await createViewConfigJson(routeMeta);\n\n await Promise.all([\n generateRuntimeFile(urls.json, viewConfigJson).then(() => {\n if (debug) {\n logger.info(`Frontend route list is generated: ${String(urls.json)}`);\n }\n }),\n generateRuntimeFile(urls.code, runtimeRoutesCode).then(() => {\n if (debug) {\n logger.info(`File Route module is generated: ${String(urls.code)}`);\n }\n }),\n ]);\n}\n"],
5
- "mappings": "AAAA,SAAS,kBAAkB;AAC3B,SAAS,OAAO,UAAU,iBAAiB;AAE3C,OAAO,yBAAyB;AAChC,OAAO,0BAA0B;AACjC,OAAO,0BAA0B;AAyBjC,eAAe,oBAAoB,KAAU,MAA6B;AACxE,QAAM,MAAM,IAAI,IAAI,MAAM,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,SAAS,KAAK,OAAO;AAAA,EACxC,SAAS,GAAY;AACnB,QAAI,EAAE,KAAK,QAAQ,OAAO,MAAM,YAAY,UAAU,KAAK,EAAE,SAAS,WAAW;AAC/E,YAAM;AAAA,IACR;AAAA,EACF;AACA,MAAI,aAAa,MAAM;AACrB,UAAM,UAAU,KAAK,MAAM,OAAO;AAAA,EACpC;AACF;AAWA,eAAsB,qBACpB,UACA,MACA,YACA,QACA,OACe;AACf,QAAM,YAAY,WAAW,QAAQ,IAAI,MAAM,oBAAoB,UAAU,EAAE,YAAY,OAAO,CAAC,IAAI,CAAC;AACxG,MAAI,OAAO;AACT,WAAO,KAAK,6BAA6B;AAAA,EAC3C;AACA,QAAM,oBAAoB,qBAAqB,WAAW,IAAI;AAC9D,QAAM,iBAAiB,MAAM,qBAAqB,SAAS;AAE3D,QAAM,QAAQ,IAAI;AAAA,IAChB,oBAAoB,KAAK,MAAM,cAAc,EAAE,KAAK,MAAM;AACxD,UAAI,OAAO;AACT,eAAO,KAAK,qCAAqC,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,MACtE;AAAA,IACF,CAAC;AAAA,IACD,oBAAoB,KAAK,MAAM,iBAAiB,EAAE,KAAK,MAAM;AAC3D,UAAI,OAAO;AACT,eAAO,KAAK,mCAAmC,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,MACpE;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;",
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;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["src/vite-plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAM3C;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC;IACxB;;;;OAIG;IACH,YAAY,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,0BAA0B,CAAC,EACjD,QAA4B,EAC5B,YAAoC,EACpC,UAA6B,EAC7B,SAAiB,EACjB,KAAa,GACd,GAAE,aAAkB,GAAG,MAAM,CA4F7B"}
1
+ {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["src/vite-plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAM3C;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC;IACxB;;;;OAIG;IACH,YAAY,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,0BAA0B,CAAC,EACjD,QAA4B,EAC5B,YAAoC,EACpC,UAA6B,EAC7B,SAAiB,EACjB,KAAa,GACd,GAAE,aAAkB,GAAG,MAAM,CA6F7B"}
package/vite-plugin.js CHANGED
@@ -28,7 +28,8 @@ function vitePluginFileSystemRouter({
28
28
  }
29
29
  runtimeUrls = {
30
30
  json: new URL("file-routes.json", isDevMode ? _generatedDir : _outDir),
31
- code: new URL("file-routes.ts", _generatedDir)
31
+ code: new URL("file-routes.ts", _generatedDir),
32
+ layouts: new URL("layouts.json", _generatedDir)
32
33
  };
33
34
  },
34
35
  async buildStart() {
@@ -43,7 +44,7 @@ function vitePluginFileSystemRouter({
43
44
  const changeListener = (file) => {
44
45
  if (!file.startsWith(dir)) {
45
46
  if (file === fileURLToPath(runtimeUrls.json)) {
46
- server.hot.send({ type: "full-reload" });
47
+ server.hot.send({ type: "custom", event: "fs-route-update" });
47
48
  }
48
49
  return;
49
50
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["src/vite-plugin.ts"],
4
- "sourcesContent": ["import { basename } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { TransformResult } from 'rollup';\nimport type { Logger, Plugin } from 'vite';\nimport { generateRuntimeFiles, type RuntimeFileUrls } from './vite-plugin/generateRuntimeFiles.js';\n\nconst INJECTION =\n \"if (Object.keys(nextExports).length === 2 && 'default' in nextExports && 'config' in nextExports) {nextExports = { ...nextExports, config: currentExports.config };}\";\n\n/**\n * The options for the Vite file-based router plugin.\n */\nexport type PluginOptions = Readonly<{\n /**\n * The base directory for the router. The folders and files in this directory\n * will be used as route paths.\n *\n * @defaultValue `frontend/views`\n */\n viewsDir?: URL | string;\n /**\n * The directory where the generated view file will be stored.\n *\n * @defaultValue `frontend/generated`\n */\n generatedDir?: URL | string;\n /**\n * The list of extensions that will be collected as routes of the file-based\n * router.\n *\n * @defaultValue `['.tsx', '.jsx']`\n */\n extensions?: readonly string[];\n /**\n * The flag to indicate whether the plugin is running in development mode.\n *\n * @defaultValue `false`\n */\n isDevMode?: boolean;\n /**\n * The flag to indicate whether to output debug information\n *\n * @defaultValue `false`\n */\n debug?: boolean;\n}>;\n\n/**\n * A Vite plugin that generates a router from the files in the specific directory.\n *\n * @param options - The plugin options.\n * @returns A Vite plugin.\n */\nexport default function vitePluginFileSystemRouter({\n viewsDir = 'frontend/views/',\n generatedDir = 'frontend/generated/',\n extensions = ['.tsx', '.jsx'],\n isDevMode = false,\n debug = false,\n}: PluginOptions = {}): Plugin {\n let _viewsDir: URL;\n let _outDir: URL;\n let _logger: Logger;\n let runtimeUrls: RuntimeFileUrls;\n\n return {\n name: 'vite-plugin-file-router',\n configResolved({ logger, root, build: { outDir } }) {\n const _root = pathToFileURL(root);\n const _generatedDir = new URL(generatedDir, _root);\n\n _viewsDir = new URL(viewsDir, _root);\n _outDir = pathToFileURL(outDir);\n _logger = logger;\n\n if (debug) {\n _logger.info(`The directory of route files: ${String(_viewsDir)}`);\n _logger.info(`The directory of generated files: ${String(_generatedDir)}`);\n _logger.info(`The output directory: ${String(_outDir)}`);\n }\n runtimeUrls = {\n json: new URL('file-routes.json', isDevMode ? _generatedDir : _outDir),\n code: new URL('file-routes.ts', _generatedDir),\n };\n },\n async buildStart() {\n try {\n await generateRuntimeFiles(_viewsDir, runtimeUrls, extensions, _logger, debug);\n } catch (e: unknown) {\n _logger.error(String(e));\n }\n },\n configureServer(server) {\n const dir = fileURLToPath(_viewsDir);\n\n const changeListener = (file: string): void => {\n if (!file.startsWith(dir)) {\n if (file === fileURLToPath(runtimeUrls.json)) {\n server.hot.send({ type: 'full-reload' });\n }\n return;\n }\n\n generateRuntimeFiles(_viewsDir, runtimeUrls, extensions, _logger, debug).catch((e: unknown) =>\n _logger.error(String(e)),\n );\n };\n\n server.watcher.on('add', changeListener);\n server.watcher.on('change', changeListener);\n server.watcher.on('unlink', changeListener);\n },\n transform(code, id): Promise<TransformResult> | TransformResult {\n let modifiedCode = code;\n if (id.startsWith(fileURLToPath(_viewsDir)) && !basename(id).startsWith('_')) {\n if (isDevMode) {\n // To enable HMR for route files with exported configurations, we need\n // to address a limitation in `react-refresh`. This library requires\n // strict equality (`===`) for non-component exports. However, the\n // dynamic nature of HMR makes maintaining this equality between object\n // literals challenging.\n //\n // To work around this, we implement a strategy that preserves the\n // reference to the original configuration object (`currentExports.config`),\n // replacing any newly created configuration objects (`nextExports.config`)\n // with it. This ensures that the HMR mechanism perceives the\n // configuration as unchanged.\n const injectionPattern = /import\\.meta\\.hot\\.accept[\\s\\S]+if\\s\\(!nextExports\\)\\s+return;/gu;\n if (injectionPattern.test(modifiedCode)) {\n modifiedCode = `${modifiedCode.substring(0, injectionPattern.lastIndex)}${INJECTION}${modifiedCode.substring(\n injectionPattern.lastIndex,\n )}`;\n }\n } else {\n // In production mode, the function name is assigned as name to the function itself to avoid minification\n const functionNames = /export\\s+default\\s+(?:function\\s+)?(\\w+)/u.exec(modifiedCode);\n\n if (functionNames?.length) {\n const [, functionName] = functionNames;\n modifiedCode += `Object.defineProperty(${functionName}, 'name', { value: '${functionName}' });\\n`;\n }\n }\n\n return {\n code: modifiedCode,\n };\n }\n\n return undefined;\n },\n };\n}\n"],
5
- "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,eAAe,qBAAqB;AAG7C,SAAS,4BAAkD;AAE3D,MAAM,YACJ;AA8Ca,SAAR,2BAA4C;AAAA,EACjD,WAAW;AAAA,EACX,eAAe;AAAA,EACf,aAAa,CAAC,QAAQ,MAAM;AAAA,EAC5B,YAAY;AAAA,EACZ,QAAQ;AACV,IAAmB,CAAC,GAAW;AAC7B,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,eAAe,EAAE,QAAQ,MAAM,OAAO,EAAE,OAAO,EAAE,GAAG;AAClD,YAAM,QAAQ,cAAc,IAAI;AAChC,YAAM,gBAAgB,IAAI,IAAI,cAAc,KAAK;AAEjD,kBAAY,IAAI,IAAI,UAAU,KAAK;AACnC,gBAAU,cAAc,MAAM;AAC9B,gBAAU;AAEV,UAAI,OAAO;AACT,gBAAQ,KAAK,iCAAiC,OAAO,SAAS,CAAC,EAAE;AACjE,gBAAQ,KAAK,qCAAqC,OAAO,aAAa,CAAC,EAAE;AACzE,gBAAQ,KAAK,yBAAyB,OAAO,OAAO,CAAC,EAAE;AAAA,MACzD;AACA,oBAAc;AAAA,QACZ,MAAM,IAAI,IAAI,oBAAoB,YAAY,gBAAgB,OAAO;AAAA,QACrE,MAAM,IAAI,IAAI,kBAAkB,aAAa;AAAA,MAC/C;AAAA,IACF;AAAA,IACA,MAAM,aAAa;AACjB,UAAI;AACF,cAAM,qBAAqB,WAAW,aAAa,YAAY,SAAS,KAAK;AAAA,MAC/E,SAAS,GAAY;AACnB,gBAAQ,MAAM,OAAO,CAAC,CAAC;AAAA,MACzB;AAAA,IACF;AAAA,IACA,gBAAgB,QAAQ;AACtB,YAAM,MAAM,cAAc,SAAS;AAEnC,YAAM,iBAAiB,CAAC,SAAuB;AAC7C,YAAI,CAAC,KAAK,WAAW,GAAG,GAAG;AACzB,cAAI,SAAS,cAAc,YAAY,IAAI,GAAG;AAC5C,mBAAO,IAAI,KAAK,EAAE,MAAM,cAAc,CAAC;AAAA,UACzC;AACA;AAAA,QACF;AAEA,6BAAqB,WAAW,aAAa,YAAY,SAAS,KAAK,EAAE;AAAA,UAAM,CAAC,MAC9E,QAAQ,MAAM,OAAO,CAAC,CAAC;AAAA,QACzB;AAAA,MACF;AAEA,aAAO,QAAQ,GAAG,OAAO,cAAc;AACvC,aAAO,QAAQ,GAAG,UAAU,cAAc;AAC1C,aAAO,QAAQ,GAAG,UAAU,cAAc;AAAA,IAC5C;AAAA,IACA,UAAU,MAAM,IAAgD;AAC9D,UAAI,eAAe;AACnB,UAAI,GAAG,WAAW,cAAc,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,WAAW,GAAG,GAAG;AAC5E,YAAI,WAAW;AAYb,gBAAM,mBAAmB;AACzB,cAAI,iBAAiB,KAAK,YAAY,GAAG;AACvC,2BAAe,GAAG,aAAa,UAAU,GAAG,iBAAiB,SAAS,CAAC,GAAG,SAAS,GAAG,aAAa;AAAA,cACjG,iBAAiB;AAAA,YACnB,CAAC;AAAA,UACH;AAAA,QACF,OAAO;AAEL,gBAAM,gBAAgB,4CAA4C,KAAK,YAAY;AAEnF,cAAI,eAAe,QAAQ;AACzB,kBAAM,CAAC,EAAE,YAAY,IAAI;AACzB,4BAAgB,yBAAyB,YAAY,uBAAuB,YAAY;AAAA;AAAA,UAC1F;AAAA,QACF;AAEA,eAAO;AAAA,UACL,MAAM;AAAA,QACR;AAAA,MACF;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { basename } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { TransformResult } from 'rollup';\nimport type { Logger, Plugin } from 'vite';\nimport { generateRuntimeFiles, type RuntimeFileUrls } from './vite-plugin/generateRuntimeFiles.js';\n\nconst INJECTION =\n \"if (Object.keys(nextExports).length === 2 && 'default' in nextExports && 'config' in nextExports) {nextExports = { ...nextExports, config: currentExports.config };}\";\n\n/**\n * The options for the Vite file-based router plugin.\n */\nexport type PluginOptions = Readonly<{\n /**\n * The base directory for the router. The folders and files in this directory\n * will be used as route paths.\n *\n * @defaultValue `frontend/views`\n */\n viewsDir?: URL | string;\n /**\n * The directory where the generated view file will be stored.\n *\n * @defaultValue `frontend/generated`\n */\n generatedDir?: URL | string;\n /**\n * The list of extensions that will be collected as routes of the file-based\n * router.\n *\n * @defaultValue `['.tsx', '.jsx']`\n */\n extensions?: readonly string[];\n /**\n * The flag to indicate whether the plugin is running in development mode.\n *\n * @defaultValue `false`\n */\n isDevMode?: boolean;\n /**\n * The flag to indicate whether to output debug information\n *\n * @defaultValue `false`\n */\n debug?: boolean;\n}>;\n\n/**\n * A Vite plugin that generates a router from the files in the specific directory.\n *\n * @param options - The plugin options.\n * @returns A Vite plugin.\n */\nexport default function vitePluginFileSystemRouter({\n viewsDir = 'frontend/views/',\n generatedDir = 'frontend/generated/',\n extensions = ['.tsx', '.jsx'],\n isDevMode = false,\n debug = false,\n}: PluginOptions = {}): Plugin {\n let _viewsDir: URL;\n let _outDir: URL;\n let _logger: Logger;\n let runtimeUrls: RuntimeFileUrls;\n\n return {\n name: 'vite-plugin-file-router',\n configResolved({ logger, root, build: { outDir } }) {\n const _root = pathToFileURL(root);\n const _generatedDir = new URL(generatedDir, _root);\n\n _viewsDir = new URL(viewsDir, _root);\n _outDir = pathToFileURL(outDir);\n _logger = logger;\n\n if (debug) {\n _logger.info(`The directory of route files: ${String(_viewsDir)}`);\n _logger.info(`The directory of generated files: ${String(_generatedDir)}`);\n _logger.info(`The output directory: ${String(_outDir)}`);\n }\n runtimeUrls = {\n json: new URL('file-routes.json', isDevMode ? _generatedDir : _outDir),\n code: new URL('file-routes.ts', _generatedDir),\n layouts: new URL('layouts.json', _generatedDir),\n };\n },\n async buildStart() {\n try {\n await generateRuntimeFiles(_viewsDir, runtimeUrls, extensions, _logger, debug);\n } catch (e: unknown) {\n _logger.error(String(e));\n }\n },\n configureServer(server) {\n const dir = fileURLToPath(_viewsDir);\n\n const changeListener = (file: string): void => {\n if (!file.startsWith(dir)) {\n if (file === fileURLToPath(runtimeUrls.json)) {\n server.hot.send({ type: 'custom', event: 'fs-route-update' });\n }\n return;\n }\n\n generateRuntimeFiles(_viewsDir, runtimeUrls, extensions, _logger, debug).catch((e: unknown) =>\n _logger.error(String(e)),\n );\n };\n\n server.watcher.on('add', changeListener);\n server.watcher.on('change', changeListener);\n server.watcher.on('unlink', changeListener);\n },\n transform(code, id): Promise<TransformResult> | TransformResult {\n let modifiedCode = code;\n if (id.startsWith(fileURLToPath(_viewsDir)) && !basename(id).startsWith('_')) {\n if (isDevMode) {\n // To enable HMR for route files with exported configurations, we need\n // to address a limitation in `react-refresh`. This library requires\n // strict equality (`===`) for non-component exports. However, the\n // dynamic nature of HMR makes maintaining this equality between object\n // literals challenging.\n //\n // To work around this, we implement a strategy that preserves the\n // reference to the original configuration object (`currentExports.config`),\n // replacing any newly created configuration objects (`nextExports.config`)\n // with it. This ensures that the HMR mechanism perceives the\n // configuration as unchanged.\n const injectionPattern = /import\\.meta\\.hot\\.accept[\\s\\S]+if\\s\\(!nextExports\\)\\s+return;/gu;\n if (injectionPattern.test(modifiedCode)) {\n modifiedCode = `${modifiedCode.substring(0, injectionPattern.lastIndex)}${INJECTION}${modifiedCode.substring(\n injectionPattern.lastIndex,\n )}`;\n }\n } else {\n // In production mode, the function name is assigned as name to the function itself to avoid minification\n const functionNames = /export\\s+default\\s+(?:function\\s+)?(\\w+)/u.exec(modifiedCode);\n\n if (functionNames?.length) {\n const [, functionName] = functionNames;\n modifiedCode += `Object.defineProperty(${functionName}, 'name', { value: '${functionName}' });\\n`;\n }\n }\n\n return {\n code: modifiedCode,\n };\n }\n\n return undefined;\n },\n };\n}\n"],
5
+ "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,eAAe,qBAAqB;AAG7C,SAAS,4BAAkD;AAE3D,MAAM,YACJ;AA8Ca,SAAR,2BAA4C;AAAA,EACjD,WAAW;AAAA,EACX,eAAe;AAAA,EACf,aAAa,CAAC,QAAQ,MAAM;AAAA,EAC5B,YAAY;AAAA,EACZ,QAAQ;AACV,IAAmB,CAAC,GAAW;AAC7B,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,eAAe,EAAE,QAAQ,MAAM,OAAO,EAAE,OAAO,EAAE,GAAG;AAClD,YAAM,QAAQ,cAAc,IAAI;AAChC,YAAM,gBAAgB,IAAI,IAAI,cAAc,KAAK;AAEjD,kBAAY,IAAI,IAAI,UAAU,KAAK;AACnC,gBAAU,cAAc,MAAM;AAC9B,gBAAU;AAEV,UAAI,OAAO;AACT,gBAAQ,KAAK,iCAAiC,OAAO,SAAS,CAAC,EAAE;AACjE,gBAAQ,KAAK,qCAAqC,OAAO,aAAa,CAAC,EAAE;AACzE,gBAAQ,KAAK,yBAAyB,OAAO,OAAO,CAAC,EAAE;AAAA,MACzD;AACA,oBAAc;AAAA,QACZ,MAAM,IAAI,IAAI,oBAAoB,YAAY,gBAAgB,OAAO;AAAA,QACrE,MAAM,IAAI,IAAI,kBAAkB,aAAa;AAAA,QAC7C,SAAS,IAAI,IAAI,gBAAgB,aAAa;AAAA,MAChD;AAAA,IACF;AAAA,IACA,MAAM,aAAa;AACjB,UAAI;AACF,cAAM,qBAAqB,WAAW,aAAa,YAAY,SAAS,KAAK;AAAA,MAC/E,SAAS,GAAY;AACnB,gBAAQ,MAAM,OAAO,CAAC,CAAC;AAAA,MACzB;AAAA,IACF;AAAA,IACA,gBAAgB,QAAQ;AACtB,YAAM,MAAM,cAAc,SAAS;AAEnC,YAAM,iBAAiB,CAAC,SAAuB;AAC7C,YAAI,CAAC,KAAK,WAAW,GAAG,GAAG;AACzB,cAAI,SAAS,cAAc,YAAY,IAAI,GAAG;AAC5C,mBAAO,IAAI,KAAK,EAAE,MAAM,UAAU,OAAO,kBAAkB,CAAC;AAAA,UAC9D;AACA;AAAA,QACF;AAEA,6BAAqB,WAAW,aAAa,YAAY,SAAS,KAAK,EAAE;AAAA,UAAM,CAAC,MAC9E,QAAQ,MAAM,OAAO,CAAC,CAAC;AAAA,QACzB;AAAA,MACF;AAEA,aAAO,QAAQ,GAAG,OAAO,cAAc;AACvC,aAAO,QAAQ,GAAG,UAAU,cAAc;AAC1C,aAAO,QAAQ,GAAG,UAAU,cAAc;AAAA,IAC5C;AAAA,IACA,UAAU,MAAM,IAAgD;AAC9D,UAAI,eAAe;AACnB,UAAI,GAAG,WAAW,cAAc,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,WAAW,GAAG,GAAG;AAC5E,YAAI,WAAW;AAYb,gBAAM,mBAAmB;AACzB,cAAI,iBAAiB,KAAK,YAAY,GAAG;AACvC,2BAAe,GAAG,aAAa,UAAU,GAAG,iBAAiB,SAAS,CAAC,GAAG,SAAS,GAAG,aAAa;AAAA,cACjG,iBAAiB;AAAA,YACnB,CAAC;AAAA,UACH;AAAA,QACF,OAAO;AAEL,gBAAM,gBAAgB,4CAA4C,KAAK,YAAY;AAEnF,cAAI,eAAe,QAAQ;AACzB,kBAAM,CAAC,EAAE,YAAY,IAAI;AACzB,4BAAgB,yBAAyB,YAAY,uBAAuB,YAAY;AAAA;AAAA,UAC1F;AAAA,QACF;AAEA,eAAO;AAAA,UACL,MAAM;AAAA,QACR;AAAA,MACF;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }