@vaadin/hilla-file-router 24.8.0-alpha2 → 24.8.0-alpha3

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.8.0-alpha2",
3
+ "version": "24.8.0-alpha3",
4
4
  "description": "Hilla file-based router",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -61,9 +61,9 @@
61
61
  "react-router": "7"
62
62
  },
63
63
  "dependencies": {
64
- "@vaadin/hilla-generator-utils": "24.8.0-alpha2",
65
- "@vaadin/hilla-react-auth": "24.8.0-alpha2",
66
- "@vaadin/hilla-react-signals": "24.8.0-alpha2",
67
- "typescript": "5.7.3"
64
+ "@vaadin/hilla-generator-utils": "24.8.0-alpha3",
65
+ "@vaadin/hilla-react-auth": "24.8.0-alpha3",
66
+ "@vaadin/hilla-react-signals": "24.8.0-alpha3",
67
+ "typescript": "5"
68
68
  }
69
69
  }
@@ -19,7 +19,7 @@ export function createMenuItems() {
19
19
  vaadinObj.registrations ??= [];
20
20
  vaadinObj.registrations.push({
21
21
  is: feature ? `@vaadin/hilla-file-router/${feature}` : "@vaadin/hilla-file-router",
22
- version: "24.8.0-alpha2"
22
+ version: "24.8.0-alpha3"
23
23
  });
24
24
  })("createMenuItems", window.Vaadin);
25
25
  const collator = new Intl.Collator("en-US");
@@ -1 +1 @@
1
- {"mappings":"AACA,SAAsB,2CAA4C;AAIlE,OAAO,MAAMA,cAAkF,OAC5F,OAAwB,QAAQ,MAClC;AAED,SAAS,WAAWC,OAA4B;AAC9C,UAAS,MAAM,MAAM;AACtB;AAED,SAAS,uBAAuBC,MAAuB;AACrD,QAAO,KAAK,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,QAAQ,WAAW,IAAI,CAAC;AAClE;;;;;;;;;AAUD,OAAO,SAAS,kBAAuC;AAGrD,EAAC,CAAC,SAAS,YAAa,OAAO,WAAW,CAAE,MAAM;AAClD,YAAU,kBAAkB,CAAE;AAC9B,YAAU,cAAc,KAAK;GAC3B,IAAI,WAAW,4BAA4B,QAAQ,IAAI;GACvD,SAAS;EACV,EAAC;CACH,GAAE,mBAAoB,OAAwB,OAAO;CACpD,MAAM,WAAW,IAAI,KAAK,SAAS;AACnC,MAAK,YAAY,OAAO;AACtB,SAAO,CAAE;CACV;CAED,MAAM,QAAQ,OAAO,QAAQ,YAAY,MAAM;AAE/C,QACE,MAEG,OAAO,CAAC,CAAC,MAAM,MAAM,MAAM,WAAW,MAAM,KAAK,uBAAuB,KAAK,CAAC,CAE9E,IAAI,CAAC,CAAC,MAAM,OAAO,MAAM;EACxB,IAAI;EACJ,MAAM,OAAO,MAAM;EACnB,OAAO,OAAO,MAAM,SAAS,OAAO;EACpC,OAAO,OAAO,MAAM;CACrB,GAAE,CAEF,KAAK,CAAC,OAAO,UAAU;EACtB,MAAM,cAAc,MAAM,SAAS,OAAO,cAAc,MAAM,SAAS,OAAO;AAC9E,SAAO,eAAe,IAAI,aAAa,SAAS,QAAQ,MAAM,IAAI,MAAM,GAAG;CAC5E,EAAC;AAEP;AAED,IAAI,OAAO,KAAK,KAAK;AACnB,QAAO,KAAK,IAAI,GAAG,mBAAmB,MAAM;AAC1C,QAAM,iBAAiB,CACpB,KAAK,OAAO,SAAS,KAAK,MAAM,CAAC,CACjC,KAAK,CAAC,SAAS;AACd,eAAY,QAAQ;EACrB,EAAC,CACD,MAAM,CAACC,MAAe;AACrB,WAAQ,MAAM,8BAA8B,EAAE;EAC/C,EAAC;CACL,EAAC;AACH","names":["viewsSignal: Signal<Readonly<Record<string, Readonly<ViewConfig>>> | undefined>","value: ViewConfig","path: string","e: unknown"],"sources":["/opt/agent/work/1af72d8adc613024/hilla/packages/ts/file-router/src/runtime/createMenuItems.ts"],"sourcesContent":["/// <reference types=\"vite/client\" />\nimport { type Signal, 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<Readonly<Record<string, Readonly<ViewConfig>>> | undefined> = signal(\n (window as VaadinWindow).Vaadin?.views,\n);\n\nfunction isExcluded(value: ViewConfig): boolean {\n return !!value.menu?.exclude;\n}\n\nfunction hasVariablePathSegment(path: string): boolean {\n return path.split('/').some((segment) => segment.startsWith(':'));\n}\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 ((feature, vaadinObj = (window.Vaadin ??= {})) => {\n vaadinObj.registrations ??= [];\n vaadinObj.registrations.push({\n is: feature ? `@vaadin/hilla-file-router/${feature}` : '@vaadin/hilla-file-router',\n version: '24.8.0-alpha2',\n });\n})('createMenuItems', (window as VaadinWindow).Vaadin);\n const collator = new Intl.Collator('en-US');\n if (!viewsSignal.value) {\n return [];\n }\n\n const views = Object.entries(viewsSignal.value);\n\n return (\n views\n // Filter out the views that are explicitly excluded from the menu.\n .filter(([path, value]) => !isExcluded(value) && !hasVariablePathSegment(path))\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\nif (import.meta.hot) {\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: unknown) => {\n console.error('Failed to fetch route info', e);\n });\n });\n}\n"],"version":3}
1
+ {"mappings":"AACA,SAAsB,2CAA4C;AAIlE,OAAO,MAAMA,cAAkF,OAC5F,OAAwB,QAAQ,MAClC;AAED,SAAS,WAAWC,OAA4B;AAC9C,UAAS,MAAM,MAAM;AACtB;AAED,SAAS,uBAAuBC,MAAuB;AACrD,QAAO,KAAK,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,QAAQ,WAAW,IAAI,CAAC;AAClE;;;;;;;;;AAUD,OAAO,SAAS,kBAAuC;AAGrD,EAAC,CAAC,SAAS,YAAa,OAAO,WAAW,CAAE,MAAM;AAClD,YAAU,kBAAkB,CAAE;AAC9B,YAAU,cAAc,KAAK;GAC3B,IAAI,WAAW,4BAA4B,QAAQ,IAAI;GACvD,SAAS;EACV,EAAC;CACH,GAAE,mBAAoB,OAAwB,OAAO;CACpD,MAAM,WAAW,IAAI,KAAK,SAAS;AACnC,MAAK,YAAY,OAAO;AACtB,SAAO,CAAE;CACV;CAED,MAAM,QAAQ,OAAO,QAAQ,YAAY,MAAM;AAE/C,QACE,MAEG,OAAO,CAAC,CAAC,MAAM,MAAM,MAAM,WAAW,MAAM,KAAK,uBAAuB,KAAK,CAAC,CAE9E,IAAI,CAAC,CAAC,MAAM,OAAO,MAAM;EACxB,IAAI;EACJ,MAAM,OAAO,MAAM;EACnB,OAAO,OAAO,MAAM,SAAS,OAAO;EACpC,OAAO,OAAO,MAAM;CACrB,GAAE,CAEF,KAAK,CAAC,OAAO,UAAU;EACtB,MAAM,cAAc,MAAM,SAAS,OAAO,cAAc,MAAM,SAAS,OAAO;AAC9E,SAAO,eAAe,IAAI,aAAa,SAAS,QAAQ,MAAM,IAAI,MAAM,GAAG;CAC5E,EAAC;AAEP;AAED,IAAI,OAAO,KAAK,KAAK;AACnB,QAAO,KAAK,IAAI,GAAG,mBAAmB,MAAM;AAC1C,QAAM,iBAAiB,CACpB,KAAK,OAAO,SAAS,KAAK,MAAM,CAAC,CACjC,KAAK,CAAC,SAAS;AACd,eAAY,QAAQ;EACrB,EAAC,CACD,MAAM,CAACC,MAAe;AACrB,WAAQ,MAAM,8BAA8B,EAAE;EAC/C,EAAC;CACL,EAAC;AACH","names":["viewsSignal: Signal<Readonly<Record<string, Readonly<ViewConfig>>> | undefined>","value: ViewConfig","path: string","e: unknown"],"sources":["/opt/agent/work/1af72d8adc613024/hilla/packages/ts/file-router/src/runtime/createMenuItems.ts"],"sourcesContent":["/// <reference types=\"vite/client\" />\nimport { type Signal, 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<Readonly<Record<string, Readonly<ViewConfig>>> | undefined> = signal(\n (window as VaadinWindow).Vaadin?.views,\n);\n\nfunction isExcluded(value: ViewConfig): boolean {\n return !!value.menu?.exclude;\n}\n\nfunction hasVariablePathSegment(path: string): boolean {\n return path.split('/').some((segment) => segment.startsWith(':'));\n}\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 ((feature, vaadinObj = (window.Vaadin ??= {})) => {\n vaadinObj.registrations ??= [];\n vaadinObj.registrations.push({\n is: feature ? `@vaadin/hilla-file-router/${feature}` : '@vaadin/hilla-file-router',\n version: '24.8.0-alpha3',\n });\n})('createMenuItems', (window as VaadinWindow).Vaadin);\n const collator = new Intl.Collator('en-US');\n if (!viewsSignal.value) {\n return [];\n }\n\n const views = Object.entries(viewsSignal.value);\n\n return (\n views\n // Filter out the views that are explicitly excluded from the menu.\n .filter(([path, value]) => !isExcluded(value) && !hasVariablePathSegment(path))\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\nif (import.meta.hot) {\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: unknown) => {\n console.error('Failed to fetch route info', e);\n });\n });\n}\n"],"version":3}
@@ -54,9 +54,7 @@ export default async function createViewConfigJson(views) {
54
54
  waitingForIdentifier = false;
55
55
  }
56
56
  }
57
- if (config === undefined) {
58
- config = { flowLayout: flowLayout ?? false };
59
- }
57
+ config ??= { flowLayout: flowLayout ?? false };
60
58
  let title;
61
59
  if (config.title) {
62
60
  ({title} = config);
@@ -1 +1 @@
1
- {"mappings":"AAAA,SAAS,kCAAmC;AAC5C,SAAS,uBAAwB;AACjC,OAAO,oBAAoC;AAC3C,SAAS,6EAA8E;AAEvF,SAAS,iDAAkD;AAG3D,SAAS,yCAAyC,oDAAqD;;;;;;AAOvG,UAAU,QAAQA,MAA6B;AAC7C,OAAM;AAEN,MAAK,MAAM,SAAS,KAAK,aAAa,EAAE;AACtC,SAAO,QAAQ,MAAM;CACtB;AACF;;;;;;;AAQD,eAAe,eAAe,qBAAqBC,OAA8C;CAC/F,MAAM,MAAM,MAAM,cAChB,OACA,MACA,OAAO,QAAQ,SACb,MAAM,QAAQ,IACZ,OAAO,IAAI,OAAO,EAAE,MAAM,MAAM,QAAQ,UAAU,YAAY,KAAK;EACjE,MAAM,cAAc,WAAW,MAAM,KAAK,SAAS,GAAG;AAEtD,OAAK,SAAS,QAAQ;AACpB,UAAO;IACL,OAAO,wCAAwC,KAAK;IACpD,QAAQ,iCAAiC,KAAK;IAC9C,UAAU;GACX;EACF;EAED,MAAM,aAAa,GAAG,iBACpB,QACA,MAAM,SAAS,QAAQ,QAAS,OAAO,EACvC,GAAG,aAAa,QAChB,KACD;EACD,IAAIC;EACJ,IAAI,uBAAuB;EAC3B,IAAIC;AAEJ,OAAK,MAAM,QAAQ,QAAQ,WAAW,EAAE;AACtC,OAAI,GAAG,sBAAsB,KAAK,IAAI,GAAG,aAAa,KAAK,KAAK,IAAI,KAAK,KAAK,SAAS,UAAU;AAC/F,QAAI,KAAK,eAAe,GAAG,0BAA0B,KAAK,YAAY,EAAE;KACtE,MAAM,OAAO,KAAK,YAAY,QAAQ,WAAW;KACjD,MAAM,SAAS,IAAI,QAAQ,GAAG,KAAK;AACnC,cAAS,OAAO,kBAAkB;AAClC,SAAI,OAAO,eAAe,WAAW;MACnC,MAAM,OAAO,KAAK,MAAM,KAAK,UAAU,OAAO,CAAC;AAE/C,WAAK,aAAa,cAAc;AAChC,eAAS;KACV;IACF;GACF,WAAU,KAAK,QAAQ,WAAW,CAAC,WAAW,iBAAiB,EAAE;AAChE,2BAAuB;GACxB,WAAU,wBAAwB,GAAG,aAAa,KAAK,EAAE;AACxD,oBAAgB,KAAK;AACrB,2BAAuB;GACxB;EACF;AAED,MAAI,WAAW,WAAW;AACxB,YAAS,EAAE,YAAY,cAAc,MAAO;EAC7C;EAED,IAAIC;AAEJ,MAAI,OAAO,OAAO;AAChB,IAAC,CAAE,MAAO,GAAG;EACd,OAAM;AACL,QAAK,eAAe;AAClB,UAAM,IAAI,OACP,YAAY,OAAO,QAAQ,OAAQ,CAAC;GAExC;AAED,WAAQ,4BAA4B,cAAc;EACnD;AAED,SAAO;GACL,OAAO,wCAAwC,KAAK;GACpD,GAAG;GACH,QAAQ,iCAAiC,OAAO,SAAS,KAAK;GAC9D;GACA,UAAU,gBAAgB,SAAS,CAAE,IAAG;EACzC;CACF,EAAC,CACH,CACJ;AAED,QAAO,KAAK,UAAU,KAAK,WAAW,EAAE;AACzC","names":["node: Node","views: readonly RouteMeta[]","config: ViewConfig | undefined","componentName: string | undefined","title: string"],"sources":["/opt/agent/work/1af72d8adc613024/hilla/packages/ts/file-router/src/vite-plugin/createViewConfigJson.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { Script } from 'node:vm';\nimport ts, { type Node } from 'typescript';\nimport { convertComponentNameToTitle } from '../shared/convertComponentNameToTitle.js';\nimport type { ServerViewConfig } from '../shared/internal.js';\nimport { transformTree } from '../shared/transformTree.js';\nimport type { ViewConfig } from '../types.js';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\nimport { convertFSRouteSegmentToURLPatternFormat, extractParameterFromRouteSegment } from './utils.js';\n\n/**\n * Walks the TypeScript AST using the deep-first search algorithm.\n *\n * @param node - The node to walk.\n */\nfunction* walkAST(node: Node): Generator<Node> {\n yield node;\n\n for (const child of node.getChildren()) {\n yield* walkAST(child);\n }\n}\n\n/**\n * Creates a map of all leaf routes to their configuration. This file is used by the server to provide server-side\n * routes along with managing the client-side routes.\n *\n * @param views - The route metadata tree.\n */\nexport default async function createViewConfigJson(views: readonly RouteMeta[]): Promise<string> {\n const res = await transformTree<readonly RouteMeta[], Promise<readonly ServerViewConfig[]>>(\n views,\n null,\n async (routes, next) =>\n await Promise.all(\n routes.map(async ({ path, file, layout, children, flowLayout }) => {\n const newChildren = children ? await next(children) : undefined;\n\n if (!file && !layout) {\n return {\n route: convertFSRouteSegmentToURLPatternFormat(path),\n params: extractParameterFromRouteSegment(path),\n children: newChildren,\n } satisfies ServerViewConfig;\n }\n\n const sourceFile = ts.createSourceFile(\n 'f.ts',\n await readFile(file ?? layout!, 'utf8'),\n ts.ScriptTarget.ESNext,\n true,\n );\n let config: ViewConfig | undefined;\n let waitingForIdentifier = false;\n let componentName: string | undefined;\n\n for (const node of walkAST(sourceFile)) {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === 'config') {\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const code = node.initializer.getText(sourceFile);\n const script = new Script(`(${code})`);\n config = script.runInThisContext() as ViewConfig;\n if (config.flowLayout === undefined) {\n const copy = JSON.parse(JSON.stringify(config));\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n copy.flowLayout = flowLayout ?? false;\n config = copy;\n }\n }\n } else if (node.getText(sourceFile).startsWith('export default')) {\n waitingForIdentifier = true;\n } else if (waitingForIdentifier && ts.isIdentifier(node)) {\n componentName = node.text;\n waitingForIdentifier = false;\n }\n }\n\n if (config === undefined) {\n config = { flowLayout: flowLayout ?? false };\n }\n\n let title: string;\n\n if (config.title) {\n ({ title } = config);\n } else {\n if (!componentName) {\n throw new Error(\n `The file \"${String(file ?? layout!)}\" must contain a default export of a component whose name will be used as title by default`,\n );\n }\n\n title = convertComponentNameToTitle(componentName);\n }\n\n return {\n route: convertFSRouteSegmentToURLPatternFormat(path),\n ...config,\n params: extractParameterFromRouteSegment(config.route ?? path),\n title,\n children: newChildren ?? (layout ? [] : undefined),\n } satisfies ServerViewConfig;\n }),\n ),\n );\n\n return JSON.stringify(res, undefined, 2);\n}\n"],"version":3}
1
+ {"mappings":"AAAA,SAAS,kCAAmC;AAC5C,SAAS,uBAAwB;AACjC,OAAO,oBAAoC;AAC3C,SAAS,6EAA8E;AAEvF,SAAS,iDAAkD;AAG3D,SAAS,yCAAyC,oDAAqD;;;;;;AAOvG,UAAU,QAAQA,MAA6B;AAC7C,OAAM;AAEN,MAAK,MAAM,SAAS,KAAK,aAAa,EAAE;AACtC,SAAO,QAAQ,MAAM;CACtB;AACF;;;;;;;AAQD,eAAe,eAAe,qBAAqBC,OAA8C;CAC/F,MAAM,MAAM,MAAM,cAChB,OACA,MACA,OAAO,QAAQ,SACb,MAAM,QAAQ,IACZ,OAAO,IAAI,OAAO,EAAE,MAAM,MAAM,QAAQ,UAAU,YAAY,KAAK;EACjE,MAAM,cAAc,WAAW,MAAM,KAAK,SAAS,GAAG;AAEtD,OAAK,SAAS,QAAQ;AACpB,UAAO;IACL,OAAO,wCAAwC,KAAK;IACpD,QAAQ,iCAAiC,KAAK;IAC9C,UAAU;GACX;EACF;EAED,MAAM,aAAa,GAAG,iBACpB,QACA,MAAM,SAAS,QAAQ,QAAS,OAAO,EACvC,GAAG,aAAa,QAChB,KACD;EACD,IAAIC;EACJ,IAAI,uBAAuB;EAC3B,IAAIC;AAEJ,OAAK,MAAM,QAAQ,QAAQ,WAAW,EAAE;AACtC,OAAI,GAAG,sBAAsB,KAAK,IAAI,GAAG,aAAa,KAAK,KAAK,IAAI,KAAK,KAAK,SAAS,UAAU;AAC/F,QAAI,KAAK,eAAe,GAAG,0BAA0B,KAAK,YAAY,EAAE;KACtE,MAAM,OAAO,KAAK,YAAY,QAAQ,WAAW;KACjD,MAAM,SAAS,IAAI,QAAQ,GAAG,KAAK;AACnC,cAAS,OAAO,kBAAkB;AAClC,SAAI,OAAO,eAAe,WAAW;MACnC,MAAM,OAAO,KAAK,MAAM,KAAK,UAAU,OAAO,CAAC;AAE/C,WAAK,aAAa,cAAc;AAChC,eAAS;KACV;IACF;GACF,WAAU,KAAK,QAAQ,WAAW,CAAC,WAAW,iBAAiB,EAAE;AAChE,2BAAuB;GACxB,WAAU,wBAAwB,GAAG,aAAa,KAAK,EAAE;AACxD,oBAAgB,KAAK;AACrB,2BAAuB;GACxB;EACF;AAED,aAAW,EAAE,YAAY,cAAc,MAAO;EAE9C,IAAIC;AAEJ,MAAI,OAAO,OAAO;AAChB,IAAC,CAAE,MAAO,GAAG;EACd,OAAM;AACL,QAAK,eAAe;AAClB,UAAM,IAAI,OACP,YAAY,OAAO,QAAQ,OAAQ,CAAC;GAExC;AAED,WAAQ,4BAA4B,cAAc;EACnD;AAED,SAAO;GACL,OAAO,wCAAwC,KAAK;GACpD,GAAG;GACH,QAAQ,iCAAiC,OAAO,SAAS,KAAK;GAC9D;GACA,UAAU,gBAAgB,SAAS,CAAE,IAAG;EACzC;CACF,EAAC,CACH,CACJ;AAED,QAAO,KAAK,UAAU,KAAK,WAAW,EAAE;AACzC","names":["node: Node","views: readonly RouteMeta[]","config: ViewConfig | undefined","componentName: string | undefined","title: string"],"sources":["/opt/agent/work/1af72d8adc613024/hilla/packages/ts/file-router/src/vite-plugin/createViewConfigJson.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { Script } from 'node:vm';\nimport ts, { type Node } from 'typescript';\nimport { convertComponentNameToTitle } from '../shared/convertComponentNameToTitle.js';\nimport type { ServerViewConfig } from '../shared/internal.js';\nimport { transformTree } from '../shared/transformTree.js';\nimport type { ViewConfig } from '../types.js';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\nimport { convertFSRouteSegmentToURLPatternFormat, extractParameterFromRouteSegment } from './utils.js';\n\n/**\n * Walks the TypeScript AST using the deep-first search algorithm.\n *\n * @param node - The node to walk.\n */\nfunction* walkAST(node: Node): Generator<Node> {\n yield node;\n\n for (const child of node.getChildren()) {\n yield* walkAST(child);\n }\n}\n\n/**\n * Creates a map of all leaf routes to their configuration. This file is used by the server to provide server-side\n * routes along with managing the client-side routes.\n *\n * @param views - The route metadata tree.\n */\nexport default async function createViewConfigJson(views: readonly RouteMeta[]): Promise<string> {\n const res = await transformTree<readonly RouteMeta[], Promise<readonly ServerViewConfig[]>>(\n views,\n null,\n async (routes, next) =>\n await Promise.all(\n routes.map(async ({ path, file, layout, children, flowLayout }) => {\n const newChildren = children ? await next(children) : undefined;\n\n if (!file && !layout) {\n return {\n route: convertFSRouteSegmentToURLPatternFormat(path),\n params: extractParameterFromRouteSegment(path),\n children: newChildren,\n } satisfies ServerViewConfig;\n }\n\n const sourceFile = ts.createSourceFile(\n 'f.ts',\n await readFile(file ?? layout!, 'utf8'),\n ts.ScriptTarget.ESNext,\n true,\n );\n let config: ViewConfig | undefined;\n let waitingForIdentifier = false;\n let componentName: string | undefined;\n\n for (const node of walkAST(sourceFile)) {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === 'config') {\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const code = node.initializer.getText(sourceFile);\n const script = new Script(`(${code})`);\n config = script.runInThisContext() as ViewConfig;\n if (config.flowLayout === undefined) {\n const copy = JSON.parse(JSON.stringify(config));\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n copy.flowLayout = flowLayout ?? false;\n config = copy;\n }\n }\n } else if (node.getText(sourceFile).startsWith('export default')) {\n waitingForIdentifier = true;\n } else if (waitingForIdentifier && ts.isIdentifier(node)) {\n componentName = node.text;\n waitingForIdentifier = false;\n }\n }\n\n config ??= { flowLayout: flowLayout ?? false };\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"],"version":3}
@@ -26,4 +26,4 @@ export type RuntimeFileUrls = Readonly<{
26
26
  * @param logger - The Vite logger instance.
27
27
  * @param debug - true to debug
28
28
  */
29
- export declare function generateRuntimeFiles(viewsDir: URL, urls: RuntimeFileUrls, extensions: readonly string[], logger: Logger, debug: boolean): Promise<void>;
29
+ export declare function generateRuntimeFiles(viewsDir: URL, urls: RuntimeFileUrls, extensions: readonly string[], logger: Logger, debug: boolean): Promise<boolean>;
@@ -65,9 +65,10 @@ export async function generateRuntimeFiles(viewsDir, urls, extensions, logger, d
65
65
  if (debug) {
66
66
  logger.info(`Frontend route list is generated: ${String(urls.json)}`);
67
67
  }
68
- await generateRuntimeFile(urls.code, runtimeRoutesCode, jsonWritten);
68
+ const codeWritten = await generateRuntimeFile(urls.code, runtimeRoutesCode, jsonWritten);
69
69
  if (debug) {
70
70
  logger.info(`File Route module is generated: ${String(urls.code)}`);
71
71
  }
72
+ return codeWritten;
72
73
  }
73
74
  //# sourceMappingURL=./generateRuntimeFiles.js.map
@@ -1 +1 @@
1
- {"mappings":"AAAA,SAAS,OAAO,UAAU,mCAAoC;AAE9D,OAAO,qCAAsC;AAC7C,OAAO,mDAAwE;AAC/E,OAAO,qDAAsD;AAC7D,OAAO,qDAAsD;;;;;;;;;;;AA+B7D,eAAe,oBAAoBA,KAAUC,MAAcC,YAAwC;AACjG,OAAM,MAAM,IAAI,IAAI,MAAM,MAAM,EAAE,WAAW,KAAM,EAAC;CACpD,IAAI,cAAc,cAAc;AAChC,MAAK,YAAY;EACf,IAAIC;AACJ,MAAI;AACF,cAAW,MAAM,SAAS,KAAK,QAAQ;EACxC,SAAQC,GAAY;AACnB,SAAM,KAAK,eAAe,MAAM,YAAY,UAAU,KAAK,EAAE,SAAS,WAAW;AAC/E,UAAM;GACP;EACF;AACD,gBAAc,aAAa;CAC5B;AACD,KAAI,aAAa;AACf,QAAM,UAAU,KAAK,MAAM,QAAQ;CACpC;AAED,QAAO;AACR;;;;;;;;;;AAYD,OAAO,eAAe,qBACpBC,UACAC,MACAC,YACAC,QACAC,OACe;CACf,IAAIC;AACJ,KAAI;AACF,cAAY,MAAM,oBAAoB,UAAU;GAAE;GAAY;EAAQ,EAAC;CACxE,SAAQN,GAAY;AACnB,MAAI,aAAa,SAAS,UAAU,KAAK,EAAE,SAAS,UAAU;AAC5D,eAAY,CAAE;EACf,OAAM;AACL,SAAM;EACP;CACF;AAED,KAAI,OAAO;AACT,SAAO,KAAK,8BAA8B;CAC3C;AACD,aAAY,MAAM,aAAa,WAAW,KAAK,QAAQ;CACvD,MAAM,oBAAoB,qBAAqB,WAAW,KAAK;CAC/D,MAAM,iBAAiB,MAAM,qBAAqB,UAAU;CAE5D,MAAM,cAAc,MAAM,oBAAoB,KAAK,MAAM,eAAe;AACxE,KAAI,OAAO;AACT,SAAO,MAAM,oCAAoC,OAAO,KAAK,KAAK,CAAC,EAAE;CACtE;AAID,OAAM,oBAAoB,KAAK,MAAM,mBAAmB,YAAY;AACpE,KAAI,OAAO;AACT,SAAO,MAAM,kCAAkC,OAAO,KAAK,KAAK,CAAC,EAAE;CACpE;AACF","names":["url: URL","data: string","forceWrite?: boolean","contents: string | undefined","e: unknown","viewsDir: URL","urls: RuntimeFileUrls","extensions: readonly string[]","logger: Logger","debug: boolean","routeMeta: readonly RouteMeta[]"],"sources":["/opt/agent/work/1af72d8adc613024/hilla/packages/ts/file-router/src/vite-plugin/generateRuntimeFiles.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport type { Logger } from 'vite';\nimport applyLayouts from './applyLayouts.js';\nimport collectRoutesFromFS, { type RouteMeta } from './collectRoutesFromFS.js';\nimport createRoutesFromMeta from './createRoutesFromMeta.js';\nimport createViewConfigJson from './createViewConfigJson.js';\n\n/**\n * The URLs of the files to generate.\n */\nexport type RuntimeFileUrls = Readonly<{\n /**\n * The URL of the JSON file with the leaf routes and their metadata. This file\n * will be processed by the server to provide the final route configuration.\n */\n json: URL;\n /**\n * The URL of the module with the routes tree in a framework-agnostic format.\n */\n code: URL;\n /**\n * The URL of the JSON file containing server layout path information.\n */\n layouts: URL;\n}>;\n\n/**\n * Generates a file conditionally. If the file already exists and its content is the same as the\n * given data, the file will not be overwritten. It is useful to avoid unnecessary server\n * reboot during development.\n *\n * @param url - The URL of the file to generate.\n * @param data - The data to write to the file.\n * @param forceWrite - true to force writing the file even if there are no changes\n * @returns true if the file was written, false otherwise.\n */\nasync function generateRuntimeFile(url: URL, data: string, forceWrite?: boolean): Promise<boolean> {\n await mkdir(new URL('./', url), { recursive: true });\n let shouldWrite = forceWrite ?? false;\n if (!forceWrite) {\n let contents: string | undefined;\n try {\n contents = await readFile(url, 'utf-8');\n } catch (e: unknown) {\n if (!(e != null && typeof e === 'object' && 'code' in e && e.code === 'ENOENT')) {\n throw e;\n }\n }\n shouldWrite = contents !== data;\n }\n if (shouldWrite) {\n await writeFile(url, data, 'utf-8');\n }\n\n return shouldWrite;\n}\n\n/**\n * Collects all file-based routes from the given directory, and based on them generates two files\n * described by {@link RuntimeFileUrls} type.\n * @param viewsDir - The directory that contains file-based routes (views).\n * @param urls - The URLs of the files to generate.\n * @param extensions - The list of extensions that will be collected as routes.\n * @param logger - The Vite logger instance.\n * @param debug - true to debug\n */\n// eslint-disable-next-line @typescript-eslint/max-params\nexport async function generateRuntimeFiles(\n viewsDir: URL,\n urls: RuntimeFileUrls,\n extensions: readonly string[],\n logger: Logger,\n debug: boolean,\n): Promise<void> {\n let routeMeta: readonly RouteMeta[];\n try {\n routeMeta = await collectRoutesFromFS(viewsDir, { extensions, logger });\n } catch (e: unknown) {\n if (e instanceof Error && 'code' in e && e.code === 'ENOENT') {\n routeMeta = [];\n } else {\n throw e;\n }\n }\n\n if (debug) {\n logger.info('Collected file-based routes');\n }\n routeMeta = await applyLayouts(routeMeta, urls.layouts);\n const runtimeRoutesCode = createRoutesFromMeta(routeMeta, urls);\n const viewConfigJson = await createViewConfigJson(routeMeta);\n\n const jsonWritten = await generateRuntimeFile(urls.json, viewConfigJson);\n if (debug) {\n logger.info(`Frontend route list is generated: ${String(urls.json)}`);\n }\n // If the metadata has changed, we need to write the TS file also to make Vite HMR updates work properly.\n // Even though the contents of the TS file does not change, the contents of the files imported in the TS\n // files changes and the routes must be re-applied to the React router\n await generateRuntimeFile(urls.code, runtimeRoutesCode, jsonWritten);\n if (debug) {\n logger.info(`File Route module is generated: ${String(urls.code)}`);\n }\n}\n"],"version":3}
1
+ {"mappings":"AAAA,SAAS,OAAO,UAAU,mCAAoC;AAE9D,OAAO,qCAAsC;AAC7C,OAAO,mDAAwE;AAC/E,OAAO,qDAAsD;AAC7D,OAAO,qDAAsD;;;;;;;;;;;AA+B7D,eAAe,oBAAoBA,KAAUC,MAAcC,YAAwC;AACjG,OAAM,MAAM,IAAI,IAAI,MAAM,MAAM,EAAE,WAAW,KAAM,EAAC;CACpD,IAAI,cAAc,cAAc;AAChC,MAAK,YAAY;EACf,IAAIC;AACJ,MAAI;AACF,cAAW,MAAM,SAAS,KAAK,QAAQ;EACxC,SAAQC,GAAY;AACnB,SAAM,KAAK,eAAe,MAAM,YAAY,UAAU,KAAK,EAAE,SAAS,WAAW;AAC/E,UAAM;GACP;EACF;AACD,gBAAc,aAAa;CAC5B;AACD,KAAI,aAAa;AACf,QAAM,UAAU,KAAK,MAAM,QAAQ;CACpC;AAED,QAAO;AACR;;;;;;;;;;AAYD,OAAO,eAAe,qBACpBC,UACAC,MACAC,YACAC,QACAC,OACkB;CAClB,IAAIC;AACJ,KAAI;AACF,cAAY,MAAM,oBAAoB,UAAU;GAAE;GAAY;EAAQ,EAAC;CACxE,SAAQN,GAAY;AACnB,MAAI,aAAa,SAAS,UAAU,KAAK,EAAE,SAAS,UAAU;AAC5D,eAAY,CAAE;EACf,OAAM;AACL,SAAM;EACP;CACF;AAED,KAAI,OAAO;AACT,SAAO,KAAK,8BAA8B;CAC3C;AACD,aAAY,MAAM,aAAa,WAAW,KAAK,QAAQ;CACvD,MAAM,oBAAoB,qBAAqB,WAAW,KAAK;CAC/D,MAAM,iBAAiB,MAAM,qBAAqB,UAAU;CAE5D,MAAM,cAAc,MAAM,oBAAoB,KAAK,MAAM,eAAe;AACxE,KAAI,OAAO;AACT,SAAO,MAAM,oCAAoC,OAAO,KAAK,KAAK,CAAC,EAAE;CACtE;CAID,MAAM,cAAc,MAAM,oBAAoB,KAAK,MAAM,mBAAmB,YAAY;AACxF,KAAI,OAAO;AACT,SAAO,MAAM,kCAAkC,OAAO,KAAK,KAAK,CAAC,EAAE;CACpE;AACD,QAAO;AACR","names":["url: URL","data: string","forceWrite?: boolean","contents: string | undefined","e: unknown","viewsDir: URL","urls: RuntimeFileUrls","extensions: readonly string[]","logger: Logger","debug: boolean","routeMeta: readonly RouteMeta[]"],"sources":["/opt/agent/work/1af72d8adc613024/hilla/packages/ts/file-router/src/vite-plugin/generateRuntimeFiles.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport type { Logger } from 'vite';\nimport applyLayouts from './applyLayouts.js';\nimport collectRoutesFromFS, { type RouteMeta } from './collectRoutesFromFS.js';\nimport createRoutesFromMeta from './createRoutesFromMeta.js';\nimport createViewConfigJson from './createViewConfigJson.js';\n\n/**\n * The URLs of the files to generate.\n */\nexport type RuntimeFileUrls = Readonly<{\n /**\n * The URL of the JSON file with the leaf routes and their metadata. This file\n * will be processed by the server to provide the final route configuration.\n */\n json: URL;\n /**\n * The URL of the module with the routes tree in a framework-agnostic format.\n */\n code: URL;\n /**\n * The URL of the JSON file containing server layout path information.\n */\n layouts: URL;\n}>;\n\n/**\n * Generates a file conditionally. If the file already exists and its content is the same as the\n * given data, the file will not be overwritten. It is useful to avoid unnecessary server\n * reboot during development.\n *\n * @param url - The URL of the file to generate.\n * @param data - The data to write to the file.\n * @param forceWrite - true to force writing the file even if there are no changes\n * @returns true if the file was written, false otherwise.\n */\nasync function generateRuntimeFile(url: URL, data: string, forceWrite?: boolean): Promise<boolean> {\n await mkdir(new URL('./', url), { recursive: true });\n let shouldWrite = forceWrite ?? false;\n if (!forceWrite) {\n let contents: string | undefined;\n try {\n contents = await readFile(url, 'utf-8');\n } catch (e: unknown) {\n if (!(e != null && typeof e === 'object' && 'code' in e && e.code === 'ENOENT')) {\n throw e;\n }\n }\n shouldWrite = contents !== data;\n }\n if (shouldWrite) {\n await writeFile(url, data, 'utf-8');\n }\n\n return shouldWrite;\n}\n\n/**\n * Collects all file-based routes from the given directory, and based on them generates two files\n * described by {@link RuntimeFileUrls} type.\n * @param viewsDir - The directory that contains file-based routes (views).\n * @param urls - The URLs of the files to generate.\n * @param extensions - The list of extensions that will be collected as routes.\n * @param logger - The Vite logger instance.\n * @param debug - true to debug\n */\n// eslint-disable-next-line @typescript-eslint/max-params\nexport async function generateRuntimeFiles(\n viewsDir: URL,\n urls: RuntimeFileUrls,\n extensions: readonly string[],\n logger: Logger,\n debug: boolean,\n): Promise<boolean> {\n let routeMeta: readonly RouteMeta[];\n try {\n routeMeta = await collectRoutesFromFS(viewsDir, { extensions, logger });\n } catch (e: unknown) {\n if (e instanceof Error && 'code' in e && e.code === 'ENOENT') {\n routeMeta = [];\n } else {\n throw e;\n }\n }\n\n if (debug) {\n logger.info('Collected file-based routes');\n }\n routeMeta = await applyLayouts(routeMeta, urls.layouts);\n const runtimeRoutesCode = createRoutesFromMeta(routeMeta, urls);\n const viewConfigJson = await createViewConfigJson(routeMeta);\n\n const jsonWritten = await generateRuntimeFile(urls.json, viewConfigJson);\n if (debug) {\n logger.info(`Frontend route list is generated: ${String(urls.json)}`);\n }\n // If the metadata has changed, we need to write the TS file also to make Vite HMR updates work properly.\n // Even though the contents of the TS file does not change, the contents of the files imported in the TS\n // files changes and the routes must be re-applied to the React router\n const codeWritten = await generateRuntimeFile(urls.code, runtimeRoutesCode, jsonWritten);\n if (debug) {\n logger.info(`File Route module is generated: ${String(urls.code)}`);\n }\n return codeWritten;\n}\n"],"version":3}
package/vite-plugin.js CHANGED
@@ -13,12 +13,16 @@ export default function vitePluginFileSystemRouter({ viewsDir = "frontend/views/
13
13
  let _outDir;
14
14
  let _logger;
15
15
  let runtimeUrls;
16
+ let _generateRuntimeFiles;
17
+ let _viewsDirPosix;
16
18
  return {
17
19
  name: "vite-plugin-file-router",
20
+ enforce: "pre",
18
21
  configResolved({ logger, root, build: { outDir } }) {
19
22
  const _root = pathToFileURL(root);
20
23
  const _generatedDir = new URL(generatedDir, _root);
21
24
  _viewsDir = new URL(viewsDir, _root);
25
+ _viewsDirPosix = fileURLToPath(_viewsDir).replaceAll("\\", "/");
22
26
  _outDir = pathToFileURL(outDir);
23
27
  _logger = logger;
24
28
  if (debug) {
@@ -31,37 +35,53 @@ export default function vitePluginFileSystemRouter({ viewsDir = "frontend/views/
31
35
  code: new URL("file-routes.ts", _generatedDir),
32
36
  layouts: new URL("layouts.json", _generatedDir)
33
37
  };
38
+ _generateRuntimeFiles = async () => {
39
+ try {
40
+ return await generateRuntimeFiles(_viewsDir, runtimeUrls, extensions, _logger, debug);
41
+ } catch (e) {
42
+ _logger.error(String(e));
43
+ return true;
44
+ }
45
+ };
34
46
  },
35
47
  async buildStart() {
36
- try {
37
- await generateRuntimeFiles(_viewsDir, runtimeUrls, extensions, _logger, debug);
38
- } catch (e) {
39
- _logger.error(String(e));
40
- }
48
+ await _generateRuntimeFiles();
41
49
  },
42
- configureServer(server) {
43
- const dir = fileURLToPath(_viewsDir);
44
- const changeListener = (file) => {
45
- if (!file.startsWith(dir)) {
46
- if (file === fileURLToPath(runtimeUrls.json)) {
47
- server.hot.send({
48
- type: "custom",
49
- event: "fs-route-update"
50
- });
51
- } else if (file !== fileURLToPath(runtimeUrls.layouts)) {
52
- return;
53
- }
50
+ async hotUpdate({ file, modules }) {
51
+ const fileUrlString = String(pathToFileURL(file));
52
+ if (fileUrlString === String(runtimeUrls.json)) {
53
+ this.environment.hot.send({
54
+ type: "custom",
55
+ event: "fs-route-update"
56
+ });
57
+ return [];
58
+ }
59
+ if (fileUrlString === String(runtimeUrls.code)) {
60
+ return [];
61
+ }
62
+ if (!file.startsWith(_viewsDirPosix)) {
63
+ if (fileUrlString !== String(runtimeUrls.layouts)) {
64
+ return;
54
65
  }
55
- generateRuntimeFiles(_viewsDir, runtimeUrls, extensions, _logger, debug).catch((e) => _logger.error(String(e)));
56
- };
57
- server.watcher.on("add", changeListener);
58
- server.watcher.on("change", changeListener);
59
- server.watcher.on("unlink", changeListener);
66
+ }
67
+ if (await _generateRuntimeFiles()) {
68
+ const mg = this.environment.moduleGraph;
69
+ const fileRoutesModules = mg.getModulesByFile(fileURLToPath(runtimeUrls.code).replaceAll("\\", "/"));
70
+ await Promise.all(Array.from(fileRoutesModules, async (fileRouteModule) => {
71
+ mg.invalidateModule(fileRouteModule);
72
+ await this.environment.fetchModule(fileRouteModule.id, undefined, { cached: false });
73
+ }));
74
+ const neverImported = modules.every((module) => module.importers.size === 0);
75
+ if (neverImported) {
76
+ return [...fileRoutesModules];
77
+ }
78
+ return [...fileRoutesModules, ...modules];
79
+ }
80
+ return undefined;
60
81
  },
61
82
  transform(code, id) {
62
83
  let modifiedCode = code;
63
- const viewsDirUsingSlashes = fileURLToPath(_viewsDir).replaceAll("\\", "/");
64
- if (id.startsWith(viewsDirUsingSlashes) && !basename(id).startsWith("_")) {
84
+ if (id.startsWith(_viewsDirPosix) && !basename(id).startsWith("_")) {
65
85
  if (isDevMode) {
66
86
  const injectionPattern = /import\.meta\.hot\.accept[\s\S]+if\s\(!nextExports\)\s+return;/gu;
67
87
  if (injectionPattern.test(modifiedCode)) {
@@ -1 +1 @@
1
- {"mappings":"AAAA,SAAS,2BAA4B;AACrC,SAAS,eAAe,+BAAgC;AAGxD,SAAS,mEAA0F;AAEnG,MAAM,YACJ;;;;;;;AA8CF,eAAe,SAAS,2BAA2B,EACjD,WAAW,mBACX,eAAe,uBACf,aAAa,CAAC,QAAQ,MAAO,GAC7B,YAAY,OACZ,QAAQ,OACM,GAAG,CAAE,GAAU;CAC7B,IAAIA;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;AAEJ,QAAO;EACL,MAAM;EACN,eAAe,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,EAAE,EAAE;GAClD,MAAM,QAAQ,cAAc,KAAK;GACjC,MAAM,gBAAgB,IAAI,IAAI,cAAc;AAE5C,eAAY,IAAI,IAAI,UAAU;AAC9B,aAAU,cAAc,OAAO;AAC/B,aAAU;AAEV,OAAI,OAAO;AACT,YAAQ,MAAM,gCAAgC,OAAO,UAAU,CAAC,EAAE;AAClE,YAAQ,MAAM,oCAAoC,OAAO,cAAc,CAAC,EAAE;AAC1E,YAAQ,MAAM,wBAAwB,OAAO,QAAQ,CAAC,EAAE;GACzD;AACD,iBAAc;IACZ,MAAM,IAAI,IAAI,oBAAoB,YAAY,gBAAgB;IAC9D,MAAM,IAAI,IAAI,kBAAkB;IAChC,SAAS,IAAI,IAAI,gBAAgB;GAClC;EACF;EACD,MAAM,aAAa;AACjB,OAAI;AACF,UAAM,qBAAqB,WAAW,aAAa,YAAY,SAAS,MAAM;GAC/E,SAAQC,GAAY;AACnB,YAAQ,MAAM,OAAO,EAAE,CAAC;GACzB;EACF;EACD,gBAAgB,QAAQ;GACtB,MAAM,MAAM,cAAc,UAAU;GAEpC,MAAM,iBAAiB,CAACC,SAAuB;AAC7C,SAAK,KAAK,WAAW,IAAI,EAAE;AACzB,SAAI,SAAS,cAAc,YAAY,KAAK,EAAE;AAC5C,aAAO,IAAI,KAAK;OAAE,MAAM;OAAU,OAAO;MAAmB,EAAC;KAC9D,WAAU,SAAS,cAAc,YAAY,QAAQ,EAAE;AAEtD;KACD;IACF;AAED,yBAAqB,WAAW,aAAa,YAAY,SAAS,MAAM,CAAC,MAAM,CAACD,MAC9E,QAAQ,MAAM,OAAO,EAAE,CAAC,CACzB;GACF;AAED,UAAO,QAAQ,GAAG,OAAO,eAAe;AACxC,UAAO,QAAQ,GAAG,UAAU,eAAe;AAC3C,UAAO,QAAQ,GAAG,UAAU,eAAe;EAC5C;EACD,UAAU,MAAM,IAAgD;GAC9D,IAAI,eAAe;GACnB,MAAM,uBAAuB,cAAc,UAAU,CAAC,WAAW,MAAM,IAAI;AAC3E,OAAI,GAAG,WAAW,qBAAqB,KAAK,SAAS,GAAG,CAAC,WAAW,IAAI,EAAE;AACxE,QAAI,WAAW;KAYb,MAAM,mBAAmB;AACzB,SAAI,iBAAiB,KAAK,aAAa,EAAE;AACvC,sBAAgB,EAAE,aAAa,UAAU,GAAG,iBAAiB,UAAU,CAAC,EAAE,UAAU,EAAE,aAAa,UACjG,iBAAiB,UAClB,CAAC;KACH;IACF,OAAM;KAEL,MAAM,gBAAgB,4CAA4C,KAAK,aAAa;AAEpF,SAAI,eAAe,QAAQ;MACzB,MAAM,GAAG,aAAa,GAAG;AACzB,uBAAiB,wBAAwB,aAAa,sBAAsB,aAAa;KAC1F;IACF;AAED,WAAO,EACL,MAAM,aACP;GACF;AAED,UAAO;EACR;CACF;AACF","names":["_viewsDir: URL","_outDir: URL","_logger: Logger","runtimeUrls: RuntimeFileUrls","e: unknown","file: string"],"sources":["/opt/agent/work/1af72d8adc613024/hilla/packages/ts/file-router/src/vite-plugin.ts"],"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 } else if (file !== fileURLToPath(runtimeUrls.layouts)) {\n // outside views folder, only changes to layouts file should trigger files generation\n return;\n }\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 const viewsDirUsingSlashes = fileURLToPath(_viewsDir).replaceAll('\\\\', '/');\n if (id.startsWith(viewsDirUsingSlashes) && !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"],"version":3}
1
+ {"mappings":"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","names":["_viewsDir: URL","_outDir: URL","_logger: Logger","runtimeUrls: RuntimeFileUrls","_generateRuntimeFiles: () => Promise<boolean>","_viewsDirPosix: string","e: unknown"],"sources":["/opt/agent/work/1af72d8adc613024/hilla/packages/ts/file-router/src/vite-plugin.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { TransformResult } from 'rollup';\nimport type { EnvironmentModuleNode, HotUpdateOptions, 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 let _generateRuntimeFiles: () => Promise<boolean>;\n let _viewsDirPosix: string;\n\n return {\n name: 'vite-plugin-file-router',\n enforce: 'pre',\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 _viewsDirPosix = fileURLToPath(_viewsDir).replaceAll('\\\\', '/');\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 _generateRuntimeFiles = async (): Promise<boolean> => {\n try {\n return await generateRuntimeFiles(_viewsDir, runtimeUrls, extensions, _logger, debug);\n } catch (e: unknown) {\n _logger.error(String(e));\n return true;\n }\n };\n },\n async buildStart() {\n await _generateRuntimeFiles();\n },\n async hotUpdate({ file, modules }: HotUpdateOptions): Promise<void | EnvironmentModuleNode[]> {\n const fileUrlString = String(pathToFileURL(file));\n\n if (fileUrlString === String(runtimeUrls.json)) {\n // Reload file routes JSON with a custom HMR event.\n this.environment.hot.send({ type: 'custom', event: 'fs-route-update' });\n return [];\n }\n\n if (fileUrlString === String(runtimeUrls.code)) {\n // Skip HMR for file routes from Vite builtin file change listener,\n // as we have our own HMR handling below.\n return [];\n }\n\n if (!file.startsWith(_viewsDirPosix)) {\n // Outside views folder, only changes to layouts file should trigger\n // files generation.\n if (fileUrlString !== String(runtimeUrls.layouts)) {\n return;\n }\n }\n\n // Check and update file routes if needed.\n if (await _generateRuntimeFiles()) {\n // The \"file-routes.ts\" file was changed at this point, so it should\n // be considered within the HMR update that caused the routes update.\n\n const mg = this.environment.moduleGraph;\n const fileRoutesModules = mg.getModulesByFile(fileURLToPath(runtimeUrls.code).replaceAll('\\\\', '/'))!;\n\n // Make eager update for file routes in Vite module graph\n // for consistency with the generated file contents.\n await Promise.all(\n Array.from(fileRoutesModules, async (fileRouteModule) => {\n mg.invalidateModule(fileRouteModule);\n await this.environment.fetchModule(fileRouteModule.id!, undefined, { cached: false });\n }),\n );\n\n const neverImported = modules.every((module) => module.importers.size === 0);\n if (neverImported) {\n // The current file is a not imported anywhere, however it caused\n // \"file-routes.ts\" update: possibly a route for this file\n // was removed, or we're processing \"layouts.json\".\n\n // Default Vite HMR behavior for not imported modules is a full page\n // reload. However, in this case, we could avoid it and only do HMR\n // for the file routes instead.\n return [...fileRoutesModules];\n }\n\n // Add \"file-routes.ts\" to current HMR module update list to reload\n // both at the same time.\n return [...fileRoutesModules, ...modules];\n }\n\n return undefined;\n },\n transform(code, id): Promise<TransformResult> | TransformResult {\n let modifiedCode = code;\n if (id.startsWith(_viewsDirPosix) && !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"],"version":3}