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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/hilla-file-router",
3
- "version": "24.4.0-beta2",
3
+ "version": "24.4.0-beta4",
4
4
  "description": "Hilla file-based router",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -74,14 +74,13 @@
74
74
  "chai-like": "^1.1.1",
75
75
  "deep-equal-in-any-order": "^2.0.6",
76
76
  "mocha": "^10.2.0",
77
- "rimraf": "^5.0.5",
78
77
  "sinon": "^16.0.0",
79
78
  "sinon-chai": "^3.7.0",
80
79
  "type-fest": "^4.9.0"
81
80
  },
82
81
  "dependencies": {
83
- "@vaadin/hilla-generator-utils": "24.4.0-beta2",
84
- "@vaadin/hilla-react-auth": "24.4.0-beta2",
82
+ "@vaadin/hilla-generator-utils": "24.4.0-beta4",
83
+ "@vaadin/hilla-react-auth": "24.4.0-beta4",
85
84
  "react": "^18.2.0",
86
85
  "rollup": "^4.12.0",
87
86
  "typescript": "5.3.2"
@@ -1 +1 @@
1
- {"version":3,"file":"RouterConfigurationBuilder.d.ts","sourceRoot":"","sources":["../src/runtime/RouterConfigurationBuilder.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAiB,MAAM,OAAO,CAAC;AAC1D,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAC;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;IAuBjE;;;;;;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;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"}
@@ -76,10 +76,20 @@ class RouterConfigurationBuilder {
76
76
  ];
77
77
  this.update(fallbackRoutes, (original, added, children) => {
78
78
  if (original) {
79
- return children ? {
79
+ if (!children) {
80
+ return original;
81
+ }
82
+ const _fallback = [...fallbackRoutes];
83
+ if (children.some(({ path }) => path === "*")) {
84
+ _fallback.shift();
85
+ }
86
+ if (children.some(({ index: i, path }) => i ?? path?.includes("?"))) {
87
+ _fallback.pop();
88
+ }
89
+ return {
80
90
  ...original,
81
- children: [...children, ...fallbackRoutes]
82
- } : original;
91
+ children: [...children, ..._fallback]
92
+ };
83
93
  }
84
94
  return added;
85
95
  });
@@ -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 = [\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 return children\n ? ({\n ...original,\n children: [...children, ...fallbackRoutes],\n } as RouteObject)\n : original;\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 } = 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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",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"createMenuItems.d.ts","sourceRoot":"","sources":["../src/runtime/createMenuItems.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,aAAa,CAAC;AAExD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,YAAY;;cAAkC,GAAG,SAAS,QAAQ,EAAE,CA6BnG"}
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;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,YAAY;;cAAkC,GAAG,SAAS,QAAQ,EAAE,CAgCnG"}
@@ -1,5 +1,13 @@
1
+ function __REGISTER__(feature, vaadinObj = window.Vaadin ??= {}) {
2
+ vaadinObj.registrations ??= [];
3
+ vaadinObj.registrations.push({
4
+ is: feature ? `${"@vaadin/hilla-file-router"}/${feature}` : "@vaadin/hilla-file-router",
5
+ version: "24.4.0-beta4"
6
+ });
7
+ }
1
8
  import { RouteParamType } from "../shared/routeParamType.js";
2
9
  function createMenuItems(vaadinObject = window.Vaadin) {
10
+ __REGISTER__("createMenuItems", vaadinObject);
3
11
  const collator = new Intl.Collator("en-US");
4
12
  return vaadinObject?.views ? Object.entries(vaadinObject.views).filter(
5
13
  ([_key, value]) => !value.menu?.exclude && !(value.params && Object.values(value.params).some((p) => p === RouteParamType.Required))
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/runtime/createMenuItems.ts"],
4
- "sourcesContent": ["import type { VaadinWindow } from '../shared/internal.js';\nimport { RouteParamType } from '../shared/routeParamType.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 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(\n ([_key, value]) =>\n !value.menu?.exclude &&\n !(value.params && Object.values(value.params).some((p) => p === RouteParamType.Required)),\n )\n // Map the views to menu items.\n .map(([path, config]) => {\n const _path = config.params\n ? Object.keys(config.params).reduce((acc, key) => acc.replaceAll(key, ''), path)\n : path;\n\n return {\n to: _path,\n icon: config.menu?.icon,\n title: config.menu?.title ?? config.title,\n order: config.menu?.order,\n };\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": "AACA,SAAS,sBAAsB;AAYxB,SAAS,gBAAgB,eAAgB,OAAwB,QAA6B;AACnG,QAAM,WAAW,IAAI,KAAK,SAAS,OAAO;AAC1C,SAAO,cAAc,QACjB,OAAO,QAAQ,aAAa,KAAK,EAE9B;AAAA,IACC,CAAC,CAAC,MAAM,KAAK,MACX,CAAC,MAAM,MAAM,WACb,EAAE,MAAM,UAAU,OAAO,OAAO,MAAM,MAAM,EAAE,KAAK,CAAC,MAAM,MAAM,eAAe,QAAQ;AAAA,EAC3F,EAEC,IAAI,CAAC,CAAC,MAAM,MAAM,MAAM;AACvB,UAAM,QAAQ,OAAO,SACjB,OAAO,KAAK,OAAO,MAAM,EAAE,OAAO,CAAC,KAAK,QAAQ,IAAI,WAAW,KAAK,EAAE,GAAG,IAAI,IAC7E;AAEJ,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,MAAM,OAAO,MAAM;AAAA,MACnB,OAAO,OAAO,MAAM,SAAS,OAAO;AAAA,MACpC,OAAO,OAAO,MAAM;AAAA,IACtB;AAAA,EACF,CAAC,EAEA,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;",
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 { RouteParamType } from '../shared/routeParamType.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(\n ([_key, value]) =>\n !value.menu?.exclude &&\n !(value.params && Object.values(value.params).some((p) => p === RouteParamType.Required)),\n )\n // Map the views to menu items.\n .map(([path, config]) => {\n const _path = config.params\n ? Object.keys(config.params).reduce((acc, key) => acc.replaceAll(key, ''), path)\n : path;\n\n return {\n to: _path,\n icon: config.menu?.icon,\n title: config.menu?.title ?? config.title,\n order: config.menu?.order,\n };\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;ACLA,SAAS,sBAAsB;AAYxB,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;AAAA,IACC,CAAC,CAAC,MAAM,KAAK,MACX,CAAC,MAAM,MAAM,WACb,EAAE,MAAM,UAAU,OAAO,OAAO,MAAM,MAAM,EAAE,KAAK,CAAC,MAAM,MAAM,eAAe,QAAQ;AAAA,EAC3F,EAEC,IAAI,CAAC,CAAC,MAAM,MAAM,MAAM;AACvB,UAAM,QAAQ,OAAO,SACjB,OAAO,KAAK,OAAO,MAAM,EAAE,OAAO,CAAC,KAAK,QAAQ,IAAI,WAAW,KAAK,EAAE,GAAG,IAAI,IAC7E;AAEJ,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,MAAM,OAAO,MAAM;AAAA,MACnB,OAAO,OAAO,MAAM,SAAS,OAAO;AAAA,MACpC,OAAO,OAAO,MAAM;AAAA,IACtB;AAAA,EACF,CAAC,EAEA,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;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"collectRoutesFromFS.d.ts","sourceRoot":"","sources":["../src/vite-plugin/collectRoutesFromFS.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,QAAQ,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,CA2E/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,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,8 @@
1
1
  import { opendir, readFile } from "node:fs/promises";
2
2
  import { basename, extname, relative } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { cleanUp } from "./utils.js";
4
+ import { RouteParamType } from "../shared/routeParamType.js";
5
+ import { cleanUp, routeParamTypeMap } from "./utils.js";
5
6
  async function checkFile(url, logger) {
6
7
  if (url) {
7
8
  const contents = await readFile(url, "utf-8");
@@ -24,8 +25,21 @@ async function collectRoutesFromFS(dir, { extensions, logger, parent = dir }) {
24
25
  if (d.name.startsWith("_")) {
25
26
  continue;
26
27
  }
28
+ const extension = extname(d.name);
29
+ const name = basename(d.name, extension);
30
+ if (extension !== "" && !extensions.includes(extension)) {
31
+ if (warningFor.includes(extension)) {
32
+ logger.warn(
33
+ `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.`
34
+ );
35
+ }
36
+ continue;
37
+ }
38
+ if (children.some(({ path: p }) => p === name)) {
39
+ throw new Error(`You cannot create a file and a directory with the same name ("${name}"). Use "@index" instead`);
40
+ }
27
41
  if (d.isDirectory()) {
28
- const directoryRoutes = await collectRoutesFromFS(new URL(`${d.name}/`, dir), {
42
+ const directoryRoutes = await collectRoutesFromFS(new URL(`${name}/`, dir), {
29
43
  extensions,
30
44
  logger,
31
45
  parent: dir
@@ -34,15 +48,7 @@ async function collectRoutesFromFS(dir, { extensions, logger, parent = dir }) {
34
48
  const [layoutRoute] = directoryRoutes;
35
49
  children.push(layoutRoute);
36
50
  } else if (directoryRoutes.length > 0) {
37
- children.push({ path: d.name, children: directoryRoutes });
38
- }
39
- continue;
40
- }
41
- if (!extensions.includes(extname(d.name))) {
42
- if (warningFor.includes(extname(d.name))) {
43
- logger.warn(
44
- `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.`
45
- );
51
+ children.push({ path: name, children: directoryRoutes });
46
52
  }
47
53
  continue;
48
54
  }
@@ -51,8 +57,10 @@ async function collectRoutesFromFS(dir, { extensions, logger, parent = dir }) {
51
57
  if (url === void 0) {
52
58
  continue;
53
59
  }
54
- const name = basename(d.name, extname(d.name));
55
- if (name === "@layout") {
60
+ const optionalParamType = routeParamTypeMap.get(RouteParamType.Optional);
61
+ if (name === "@index" && children.some(({ path: p }) => p.search(optionalParamType) >= 0) || name.search(optionalParamType) >= 0 && children.some(({ path: p }) => p === "")) {
62
+ throw new Error("You cannot create an `@index` file in a directory with optional parameters");
63
+ } else if (name === "@layout") {
56
64
  layout = file;
57
65
  } else if (name === "@index") {
58
66
  children.push({
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/vite-plugin/collectRoutesFromFS.ts"],
4
- "sourcesContent": ["import { opendir, readFile } from 'node:fs/promises';\nimport { basename, extname, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Logger } from 'vite';\nimport { cleanUp } from './utils.js';\n\nexport type RouteMeta = Readonly<{\n path: string;\n file?: URL;\n layout?: URL;\n children?: 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 if (d.isDirectory()) {\n const directoryRoutes = await collectRoutesFromFS(new URL(`${d.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: d.name, children: directoryRoutes });\n }\n continue;\n }\n\n if (!extensions.includes(extname(d.name))) {\n if (warningFor.includes(extname(d.name))) {\n logger.warn(\n `File System based router expects only JSX files in 'Frontend/views/' directory, such as '*.tsx' and '*.jsx'. The file '${d.name}' will be ignored by router, as it doesn't match this convention. Please consider storing it in another directory.`,\n );\n }\n 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 name = basename(d.name, extname(d.name));\n\n 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": "AAAA,SAAS,SAAS,gBAAgB;AAClC,SAAS,UAAU,SAAS,gBAAgB;AAC5C,SAAS,qBAAqB;AAE9B,SAAS,eAAe;AA4BxB,eAAe,UAAU,KAAsB,QAA0C;AACvF,MAAI,KAAK;AACP,UAAM,WAAW,MAAM,SAAS,KAAK,OAAO;AAC5C,QAAI,SAAS,KAAK,MAAM,IAAI;AAC1B,aAAO;AAAA,IACT,WAAW,CAAC,SAAS,SAAS,gBAAgB,GAAG;AAC/C,aAAO,MAAM,aAAa,OAAO,GAAG,CAAC,kDAAkD;AACvF,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,MAAM,WAAW,IAAI,KAAK,SAAS,OAAO;AAE1C,MAAM,aAAa,CAAC,OAAO,KAAK;AAgBhC,eAAO,oBACL,KACA,EAAE,YAAY,QAAQ,SAAS,IAAI,GACJ;AAC/B,QAAM,OAAO,SAAS,cAAc,MAAM,GAAG,cAAc,GAAG,CAAC;AAC/D,MAAI,WAAwB,CAAC;AAC7B,MAAI;AAEJ,mBAAiB,KAAK,MAAM,QAAQ,GAAG,GAAG;AACxC,QAAI,EAAE,KAAK,WAAW,GAAG,GAAG;AAC1B;AAAA,IACF;AAEA,QAAI,EAAE,YAAY,GAAG;AACnB,YAAM,kBAAkB,MAAM,oBAAoB,IAAI,IAAI,GAAG,EAAE,IAAI,KAAK,GAAG,GAAG;AAAA,QAC5E;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,MACV,CAAC;AACD,UAAI,gBAAgB,WAAW,KAAK,gBAAgB,CAAC,EAAE,QAAQ;AAC7D,cAAM,CAAC,WAAW,IAAI;AACtB,iBAAS,KAAK,WAAW;AAAA,MAC3B,WAAW,gBAAgB,SAAS,GAAG;AACrC,iBAAS,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,gBAAgB,CAAC;AAAA,MAC3D;AACA;AAAA,IACF;AAEA,QAAI,CAAC,WAAW,SAAS,QAAQ,EAAE,IAAI,CAAC,GAAG;AACzC,UAAI,WAAW,SAAS,QAAQ,EAAE,IAAI,CAAC,GAAG;AACxC,eAAO;AAAA,UACL,0HAA0H,EAAE,IAAI;AAAA,QAClI;AAAA,MACF;AACA;AAAA,IACF;AAEA,UAAM,OAAO,IAAI,IAAI,EAAE,MAAM,GAAG;AAChC,UAAM,MAAM,MAAM,UAAU,MAAM,MAAM;AACxC,QAAI,QAAQ,QAAW;AACrB;AAAA,IACF;AACA,UAAM,OAAO,SAAS,EAAE,MAAM,QAAQ,EAAE,IAAI,CAAC;AAE7C,QAAI,SAAS,WAAW;AACtB,eAAS;AAAA,IACX,WAAW,SAAS,UAAU;AAC5B,eAAS,KAAK;AAAA,QACZ,MAAM;AAAA,QACN;AAAA,MACF,CAAC;AAAA,IACH,WAAW,KAAK,WAAW,GAAG,GAAG;AAC/B,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF,OAAO;AACL,eAAS,KAAK;AAAA,QACZ,MAAM;AAAA,QACN;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,GAAC,UAAU,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,IACrC,QAAQ;AAAA,MACN,SAAS,IAAI,OAAO,WAAW;AAAA,QAC7B,GAAG;AAAA,QACH,MAAM,MAAM;AAAA,QACZ,QAAQ,MAAM,UAAU,MAAM,QAAQ,MAAM;AAAA,MAC9C,EAAE;AAAA,IACJ;AAAA,IACA,UAAU,QAAQ,MAAM;AAAA,EAC1B,CAAC;AAED,aAAW,SAAS,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,SAAS,QAAQ,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;AAG/F,SAAO,SAAS,CAAC,EAAE,MAAM,QAAQ,SAAS,CAAC,IAAI;AACjD;",
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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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,5 @@
1
1
  import { RouteParamType } from '../shared/routeParamType.js';
2
+ export declare const routeParamTypeMap: ReadonlyMap<RouteParamType, RegExp>;
2
3
  /**
3
4
  * Converts a file system pattern to an URL pattern string.
4
5
  *
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/vite-plugin/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAqB7D;;;;;;;;;;;;GAYG;AACH,wBAAgB,uCAAuC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQ/E;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAc1G;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQ5C"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/vite-plugin/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,cAAc,EAAE,MAAM,CAIhE,CAAC;AAeH;;;;;;;;;;;;GAYG;AACH,wBAAgB,uCAAuC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQ/E;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAc1G;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQ5C"}
@@ -1,8 +1,11 @@
1
1
  import { RouteParamType } from "../shared/routeParamType.js";
2
2
  const routeParamTypeMap = /* @__PURE__ */ new Map([
3
- [RouteParamType.Wildcard, /\{\.{3}(.+)\}/gu],
4
- [RouteParamType.Optional, /\{{2}(.+)\}{2}/gu],
3
+ [RouteParamType.Wildcard, /\{\.\.\.(.+)\}/gu],
4
+ // e.g. "{...wildcard}"
5
+ [RouteParamType.Optional, /\{\{(.+)\}\}/gu],
6
+ // e.g. "{{param}}"
5
7
  [RouteParamType.Required, /\{(.+)\}/gu]
8
+ // e.g. "{param}"
6
9
  ]);
7
10
  function getReplacer(type) {
8
11
  switch (type) {
@@ -44,6 +47,7 @@ function cleanUp(path) {
44
47
  export {
45
48
  cleanUp,
46
49
  convertFSRouteSegmentToURLPatternFormat,
47
- extractParameterFromRouteSegment
50
+ extractParameterFromRouteSegment,
51
+ routeParamTypeMap
48
52
  };
49
53
  //# sourceMappingURL=utils.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/vite-plugin/utils.ts"],
4
- "sourcesContent": ["import { RouteParamType } from '../shared/routeParamType.js';\n\nconst routeParamTypeMap: ReadonlyMap<RouteParamType, RegExp> = new Map([\n [RouteParamType.Wildcard, /\\{\\.{3}(.+)\\}/gu],\n [RouteParamType.Optional, /\\{{2}(.+)\\}{2}/gu],\n [RouteParamType.Required, /\\{(.+)\\}/gu],\n]);\n\n// eslint-disable-next-line consistent-return\nfunction getReplacer(type: RouteParamType): string {\n // eslint-disable-next-line default-case\n switch (type) {\n case RouteParamType.Wildcard:\n return '*';\n case RouteParamType.Optional:\n return ':$1?';\n case RouteParamType.Required:\n return ':$1';\n }\n}\n\n/**\n * Converts a file system pattern to an URL pattern string.\n *\n * @param segment - a string representing a file system pattern:\n * - `{param}` - for a required single parameter;\n * - `{{param}}` - for an optional single parameter;\n * - `{...wildcard}` - for multiple parameters, including none.\n *\n * @returns a string representing a URL pattern, respectively:\n * - `:param`;\n * - `:param?`;\n * - `*`.\n */\nexport function convertFSRouteSegmentToURLPatternFormat(segment: string): string {\n let res = segment;\n\n routeParamTypeMap.forEach((pattern, type) => {\n res = res.replaceAll(pattern, getReplacer(type));\n });\n\n return res;\n}\n\n/**\n * Extracts the parameter name and its type from the route segment.\n *\n * @param segment - A part of the FS route URL.\n * @returns A map of parameter names and their types.\n */\nexport function extractParameterFromRouteSegment(segment: string): Readonly<Record<string, RouteParamType>> {\n let _segment = segment;\n const params: Record<string, RouteParamType> = {};\n\n for (const [type, pattern] of routeParamTypeMap) {\n const _pattern = new RegExp(pattern.source, pattern.flags);\n _segment = _segment.replaceAll(_pattern, (match) => {\n const key = match.replaceAll(pattern, getReplacer(type));\n params[key] = type;\n return '';\n });\n }\n\n return params;\n}\n\n/**\n * A small helper function that clears route path of the control characters in\n * order to sort the routes alphabetically.\n */\nexport function cleanUp(path: string): string {\n let res = path;\n\n for (const pattern of routeParamTypeMap.values()) {\n res = res.replaceAll(pattern, '$1');\n }\n\n return res;\n}\n"],
5
- "mappings": "AAAA,SAAS,sBAAsB;AAE/B,MAAM,oBAAyD,oBAAI,IAAI;AAAA,EACrE,CAAC,eAAe,UAAU,iBAAiB;AAAA,EAC3C,CAAC,eAAe,UAAU,kBAAkB;AAAA,EAC5C,CAAC,eAAe,UAAU,YAAY;AACxC,CAAC;AAGD,SAAS,YAAY,MAA8B;AAEjD,UAAQ,MAAM;AAAA,IACZ,KAAK,eAAe;AAClB,aAAO;AAAA,IACT,KAAK,eAAe;AAClB,aAAO;AAAA,IACT,KAAK,eAAe;AAClB,aAAO;AAAA,EACX;AACF;AAeO,SAAS,wCAAwC,SAAyB;AAC/E,MAAI,MAAM;AAEV,oBAAkB,QAAQ,CAAC,SAAS,SAAS;AAC3C,UAAM,IAAI,WAAW,SAAS,YAAY,IAAI,CAAC;AAAA,EACjD,CAAC;AAED,SAAO;AACT;AAQO,SAAS,iCAAiC,SAA2D;AAC1G,MAAI,WAAW;AACf,QAAM,SAAyC,CAAC;AAEhD,aAAW,CAAC,MAAM,OAAO,KAAK,mBAAmB;AAC/C,UAAM,WAAW,IAAI,OAAO,QAAQ,QAAQ,QAAQ,KAAK;AACzD,eAAW,SAAS,WAAW,UAAU,CAAC,UAAU;AAClD,YAAM,MAAM,MAAM,WAAW,SAAS,YAAY,IAAI,CAAC;AACvD,aAAO,GAAG,IAAI;AACd,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAMO,SAAS,QAAQ,MAAsB;AAC5C,MAAI,MAAM;AAEV,aAAW,WAAW,kBAAkB,OAAO,GAAG;AAChD,UAAM,IAAI,WAAW,SAAS,IAAI;AAAA,EACpC;AAEA,SAAO;AACT;",
4
+ "sourcesContent": ["import { RouteParamType } from '../shared/routeParamType.js';\n\nexport const routeParamTypeMap: ReadonlyMap<RouteParamType, RegExp> = new Map([\n [RouteParamType.Wildcard, /\\{\\.\\.\\.(.+)\\}/gu], // e.g. \"{...wildcard}\"\n [RouteParamType.Optional, /\\{\\{(.+)\\}\\}/gu], // e.g. \"{{param}}\"\n [RouteParamType.Required, /\\{(.+)\\}/gu], // e.g. \"{param}\"\n]);\n\n// eslint-disable-next-line consistent-return\nfunction getReplacer(type: RouteParamType): string {\n // eslint-disable-next-line default-case\n switch (type) {\n case RouteParamType.Wildcard:\n return '*';\n case RouteParamType.Optional:\n return ':$1?';\n case RouteParamType.Required:\n return ':$1';\n }\n}\n\n/**\n * Converts a file system pattern to an URL pattern string.\n *\n * @param segment - a string representing a file system pattern:\n * - `{param}` - for a required single parameter;\n * - `{{param}}` - for an optional single parameter;\n * - `{...wildcard}` - for multiple parameters, including none.\n *\n * @returns a string representing a URL pattern, respectively:\n * - `:param`;\n * - `:param?`;\n * - `*`.\n */\nexport function convertFSRouteSegmentToURLPatternFormat(segment: string): string {\n let res = segment;\n\n routeParamTypeMap.forEach((pattern, type) => {\n res = res.replaceAll(pattern, getReplacer(type));\n });\n\n return res;\n}\n\n/**\n * Extracts the parameter name and its type from the route segment.\n *\n * @param segment - A part of the FS route URL.\n * @returns A map of parameter names and their types.\n */\nexport function extractParameterFromRouteSegment(segment: string): Readonly<Record<string, RouteParamType>> {\n let _segment = segment;\n const params: Record<string, RouteParamType> = {};\n\n for (const [type, pattern] of routeParamTypeMap) {\n const _pattern = new RegExp(pattern.source, pattern.flags);\n _segment = _segment.replaceAll(_pattern, (match) => {\n const key = match.replaceAll(pattern, getReplacer(type));\n params[key] = type;\n return '';\n });\n }\n\n return params;\n}\n\n/**\n * A small helper function that clears route path of the control characters in\n * order to sort the routes alphabetically.\n */\nexport function cleanUp(path: string): string {\n let res = path;\n\n for (const pattern of routeParamTypeMap.values()) {\n res = res.replaceAll(pattern, '$1');\n }\n\n return res;\n}\n"],
5
+ "mappings": "AAAA,SAAS,sBAAsB;AAExB,MAAM,oBAAyD,oBAAI,IAAI;AAAA,EAC5E,CAAC,eAAe,UAAU,kBAAkB;AAAA;AAAA,EAC5C,CAAC,eAAe,UAAU,gBAAgB;AAAA;AAAA,EAC1C,CAAC,eAAe,UAAU,YAAY;AAAA;AACxC,CAAC;AAGD,SAAS,YAAY,MAA8B;AAEjD,UAAQ,MAAM;AAAA,IACZ,KAAK,eAAe;AAClB,aAAO;AAAA,IACT,KAAK,eAAe;AAClB,aAAO;AAAA,IACT,KAAK,eAAe;AAClB,aAAO;AAAA,EACX;AACF;AAeO,SAAS,wCAAwC,SAAyB;AAC/E,MAAI,MAAM;AAEV,oBAAkB,QAAQ,CAAC,SAAS,SAAS;AAC3C,UAAM,IAAI,WAAW,SAAS,YAAY,IAAI,CAAC;AAAA,EACjD,CAAC;AAED,SAAO;AACT;AAQO,SAAS,iCAAiC,SAA2D;AAC1G,MAAI,WAAW;AACf,QAAM,SAAyC,CAAC;AAEhD,aAAW,CAAC,MAAM,OAAO,KAAK,mBAAmB;AAC/C,UAAM,WAAW,IAAI,OAAO,QAAQ,QAAQ,QAAQ,KAAK;AACzD,eAAW,SAAS,WAAW,UAAU,CAAC,UAAU;AAClD,YAAM,MAAM,MAAM,WAAW,SAAS,YAAY,IAAI,CAAC;AACvD,aAAO,GAAG,IAAI;AACd,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAMO,SAAS,QAAQ,MAAsB;AAC5C,MAAI,MAAM;AAEV,aAAW,WAAW,kBAAkB,OAAO,GAAG;AAChD,UAAM,IAAI,WAAW,SAAS,IAAI;AAAA,EACpC;AAEA,SAAO;AACT;",
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;AAG3C;;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;CACrB,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,0BAA0B,CAAC,EACjD,QAA4B,EAC5B,YAAoC,EACpC,UAA6B,EAC7B,SAAiB,GAClB,GAAE,aAAkB,GAAG,MAAM,CA8F7B"}
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;CACrB,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,0BAA0B,CAAC,EACjD,QAA4B,EAC5B,YAAoC,EACpC,UAA6B,EAC7B,SAAiB,GAClB,GAAE,aAAkB,GAAG,MAAM,CA2F7B"}
package/vite-plugin.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { basename } from "node:path";
2
2
  import { fileURLToPath, pathToFileURL } from "node:url";
3
3
  import { generateRuntimeFiles } from "./vite-plugin/generateRuntimeFiles.js";
4
+ const INJECTION = "if (Object.keys(nextExports).length === 2 && 'default' in nextExports && 'config' in nextExports) {nextExports = { ...nextExports, config: currentExports.config };}";
4
5
  function vitePluginFileSystemRouter({
5
6
  viewsDir = "frontend/views/",
6
7
  generatedDir = "frontend/generated/",
7
8
  extensions = [".tsx", ".jsx"],
8
9
  isDevMode = false
9
10
  } = {}) {
10
- const hmrInjectionPattern = /(?<=import\.meta\.hot\.accept[\s\S]+)if\s\(!nextExports\)\s+return;/u;
11
11
  let _viewsDir;
12
12
  let _outDir;
13
13
  let _logger;
@@ -56,13 +56,12 @@ function vitePluginFileSystemRouter({
56
56
  let modifiedCode = code;
57
57
  if (id.startsWith(fileURLToPath(_viewsDir)) && !basename(id).startsWith("_")) {
58
58
  if (isDevMode) {
59
- modifiedCode = modifiedCode.replace(
60
- hmrInjectionPattern,
61
- `if (!nextExports) return;
62
- if (Object.keys(nextExports).length === 2 && 'default' in nextExports && 'config' in nextExports) {
63
- nextExports = { ...nextExports, config: currentExports.config };
64
- }`
65
- );
59
+ const injectionPattern = /import\.meta\.hot\.accept[\s\S]+if\s\(!nextExports\)\s+return;/gu;
60
+ if (injectionPattern.test(modifiedCode)) {
61
+ modifiedCode = `${modifiedCode.substring(0, injectionPattern.lastIndex)}${INJECTION}${modifiedCode.substring(
62
+ injectionPattern.lastIndex
63
+ )}`;
64
+ }
66
65
  } else {
67
66
  const functionNames = /export\s+default\s+(?:function\s+)?(\w+)/u.exec(modifiedCode);
68
67
  if (functionNames?.length) {
@@ -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\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\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}: PluginOptions = {}): Plugin {\n const hmrInjectionPattern = /(?<=import\\.meta\\.hot\\.accept[\\s\\S]+)if\\s\\(!nextExports\\)\\s+return;/u;\n\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 _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);\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).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 modifiedCode = modifiedCode.replace(\n hmrInjectionPattern,\n `if (!nextExports) return;\n if (Object.keys(nextExports).length === 2 && 'default' in nextExports && 'config' in nextExports) {\n nextExports = { ...nextExports, config: currentExports.config };\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;AAwC5C,SAAR,2BAA4C;AAAA,EACjD,WAAW;AAAA,EACX,eAAe;AAAA,EACf,aAAa,CAAC,QAAQ,MAAM;AAAA,EAC5B,YAAY;AACd,IAAmB,CAAC,GAAW;AAC7B,QAAM,sBAAsB;AAE5B,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,cAAQ,KAAK,iCAAiC,OAAO,SAAS,CAAC,EAAE;AACjE,cAAQ,KAAK,qCAAqC,OAAO,aAAa,CAAC,EAAE;AACzE,cAAQ,KAAK,yBAAyB,OAAO,OAAO,CAAC,EAAE;AAEvD,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,OAAO;AAAA,MACxE,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,OAAO,EAAE;AAAA,UAAM,CAAC,MACvE,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,yBAAe,aAAa;AAAA,YAC1B;AAAA,YACA;AAAA;AAAA;AAAA;AAAA,UAIF;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\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}: 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 _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);\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).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;AAwCa,SAAR,2BAA4C;AAAA,EACjD,WAAW;AAAA,EACX,eAAe;AAAA,EACf,aAAa,CAAC,QAAQ,MAAM;AAAA,EAC5B,YAAY;AACd,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,cAAQ,KAAK,iCAAiC,OAAO,SAAS,CAAC,EAAE;AACjE,cAAQ,KAAK,qCAAqC,OAAO,aAAa,CAAC,EAAE;AACzE,cAAQ,KAAK,yBAAyB,OAAO,OAAO,CAAC,EAAE;AAEvD,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,OAAO;AAAA,MACxE,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,OAAO,EAAE;AAAA,UAAM,CAAC,MACvE,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
  }