@analogjs/vite-plugin-nitro 3.0.0-alpha.7 → 3.0.0-alpha.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/package.json +31 -11
  2. package/src/index.d.ts +19 -9
  3. package/src/index.js +9 -2
  4. package/src/index.js.map +1 -1
  5. package/src/lib/build-server.d.ts +2 -2
  6. package/src/lib/build-server.js +45 -149
  7. package/src/lib/build-server.js.map +1 -1
  8. package/src/lib/build-sitemap.d.ts +23 -9
  9. package/src/lib/build-sitemap.js +132 -63
  10. package/src/lib/build-sitemap.js.map +1 -1
  11. package/src/lib/build-ssr.d.ts +3 -2
  12. package/src/lib/build-ssr.js +26 -18
  13. package/src/lib/build-ssr.js.map +1 -1
  14. package/src/lib/hooks/post-rendering-hook.d.ts +1 -1
  15. package/src/lib/hooks/post-rendering-hook.js +10 -6
  16. package/src/lib/hooks/post-rendering-hook.js.map +1 -1
  17. package/src/lib/options.d.ts +148 -106
  18. package/src/lib/plugins/dev-server-plugin.d.ts +3 -3
  19. package/src/lib/plugins/dev-server-plugin.js +99 -101
  20. package/src/lib/plugins/dev-server-plugin.js.map +1 -1
  21. package/src/lib/plugins/page-endpoints.d.ts +5 -5
  22. package/src/lib/plugins/page-endpoints.js +26 -46
  23. package/src/lib/plugins/page-endpoints.js.map +1 -1
  24. package/src/lib/plugins/server-fn-id-plugin.d.ts +11 -0
  25. package/src/lib/plugins/server-fn-id-plugin.js +27 -0
  26. package/src/lib/plugins/server-fn-id-plugin.js.map +1 -0
  27. package/src/lib/utils/debug.d.ts +5 -0
  28. package/src/lib/utils/debug.js +15 -0
  29. package/src/lib/utils/debug.js.map +1 -0
  30. package/src/lib/utils/derive-server-fn-id.d.ts +25 -0
  31. package/src/lib/utils/derive-server-fn-id.js +37 -0
  32. package/src/lib/utils/derive-server-fn-id.js.map +1 -0
  33. package/src/lib/utils/get-content-files.d.ts +55 -55
  34. package/src/lib/utils/get-content-files.js +95 -97
  35. package/src/lib/utils/get-content-files.js.map +1 -1
  36. package/src/lib/utils/get-page-handlers.d.ts +55 -58
  37. package/src/lib/utils/get-page-handlers.js +67 -84
  38. package/src/lib/utils/get-page-handlers.js.map +1 -1
  39. package/src/lib/utils/get-server-fn-handlers.d.ts +28 -0
  40. package/src/lib/utils/get-server-fn-handlers.js +45 -0
  41. package/src/lib/utils/get-server-fn-handlers.js.map +1 -0
  42. package/src/lib/utils/i18n-prerender.d.ts +36 -0
  43. package/src/lib/utils/i18n-prerender.js +23 -0
  44. package/src/lib/utils/i18n-prerender.js.map +1 -0
  45. package/src/lib/utils/inject-server-fn-ids.d.ts +17 -0
  46. package/src/lib/utils/inject-server-fn-ids.js +94 -0
  47. package/src/lib/utils/inject-server-fn-ids.js.map +1 -0
  48. package/src/lib/utils/load-esm.d.ts +18 -18
  49. package/src/lib/utils/node-web-bridge.d.ts +1 -1
  50. package/src/lib/utils/node-web-bridge.js +50 -45
  51. package/src/lib/utils/node-web-bridge.js.map +1 -1
  52. package/src/lib/utils/register-dev-middleware.d.ts +12 -12
  53. package/src/lib/utils/register-dev-middleware.js +41 -47
  54. package/src/lib/utils/register-dev-middleware.js.map +1 -1
  55. package/src/lib/utils/register-i18n-watcher.d.ts +15 -0
  56. package/src/lib/utils/register-i18n-watcher.js +27 -0
  57. package/src/lib/utils/register-i18n-watcher.js.map +1 -0
  58. package/src/lib/utils/renderers.d.ts +29 -49
  59. package/src/lib/utils/renderers.js +37 -80
  60. package/src/lib/utils/renderers.js.map +1 -1
  61. package/src/lib/utils/rolldown.d.ts +2 -0
  62. package/src/lib/utils/rolldown.js +12 -0
  63. package/src/lib/utils/rolldown.js.map +1 -0
  64. package/src/lib/utils/server-fn-endpoints.d.ts +41 -0
  65. package/src/lib/utils/server-fn-endpoints.js +57 -0
  66. package/src/lib/utils/server-fn-endpoints.js.map +1 -0
  67. package/src/lib/vite-plugin-nitro.d.ts +3 -3
  68. package/src/lib/vite-plugin-nitro.js +793 -678
  69. package/src/lib/vite-plugin-nitro.js.map +1 -1
  70. package/README.md +0 -125
  71. package/src/lib/options.js +0 -2
  72. package/src/lib/options.js.map +0 -1
  73. package/src/lib/utils/load-esm.js +0 -23
  74. package/src/lib/utils/load-esm.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"get-content-files.js","sourceRoot":"","sources":["../../../../../../packages/vite-plugin-nitro/src/lib/utils/get-content-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,MAAM,UAAU,sCAAsC,CACpD,aAAqB,EACrB,OAAe,EACf,IAAY;IAEZ,2CAA2C;IAC3C,8DAA8D;IAC9D,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAEnC,+DAA+D;IAC/D,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC;IAE5D,kEAAkE;IAClE,MAAM,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAEpE,wDAAwD;IACxD,yDAAyD;IACzD,MAAM,YAAY,GAAa,QAAQ,CAAC,CAAC,GAAG,IAAI,IAAI,WAAW,IAAI,CAAC,EAAE;QACpE,GAAG,EAAE,IAAI;QACT,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC;IAEH,4EAA4E;IAC5E,MAAM,iBAAiB,GAA2B,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACvE,uCAAuC;QACvC,MAAM,YAAY,GAAG,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAE7C,2CAA2C;QAC3C,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC;QAE7B,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAEpD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QACzD,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,SAAS,GAAG,EAAE,CAAC;QACnB,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,8BAA8B;YAC/C,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,iDAAiD;QAC/E,CAAC;QAED,uDAAuD;QACvD,OAAO;YACL,IAAI;YACJ,SAAS;YACT,IAAI,EAAE,WAAW;YACjB,UAAU,EAAE,GAAG,CAAC,UAAiD;YACjE,OAAO,EAAE,YAAY;SACtB,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,iBAAiB,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"get-content-files.js","names":[],"sources":["../../../../src/lib/utils/get-content-files.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { join, relative, resolve } from 'node:path';\nimport { normalizePath } from 'vite';\nimport { createRequire } from 'node:module';\nimport { globSync } from 'tinyglobby';\n\nimport { PrerenderContentFile } from '../options';\n\nconst require = createRequire(import.meta.url);\n\n/**\n * Discovers content files with front matter and extracts metadata for prerendering.\n *\n * This function:\n * 1. Discovers all content files matching the specified glob pattern\n * 2. Reads each file and parses front matter metadata\n * 3. Extracts file name, extension, and path information\n * 4. Returns structured data for prerendering content pages\n *\n * @param workspaceRoot The workspace root directory path\n * @param rootDir The project root directory relative to workspace\n * @param glob The glob pattern to match content files (e.g., 'content/blog')\n * @returns Array of PrerenderContentFile objects with metadata and front matter\n *\n * Example usage:\n * const contentFiles = getMatchingContentFilesWithFrontMatter(\n * '/workspace',\n * 'apps/my-app',\n * 'content/blog'\n * );\n *\n * Sample discovered file paths:\n * - /workspace/apps/my-app/content/blog/first-post.md\n * - /workspace/apps/my-app/content/blog/2024/01/hello-world.md\n * - /workspace/apps/my-app/content/blog/tech/angular-v17.mdx\n * - /workspace/apps/my-app/content/blog/about/index.md\n *\n * Sample output structure:\n * {\n * name: 'first-post',\n * extension: 'md',\n * path: 'content/blog',\n * attributes: { title: 'My First Post', date: '2024-01-01', tags: ['intro'] }\n * }\n *\n * tinyglobby vs fast-glob comparison:\n * - Both support the same glob patterns for file discovery\n * - Both are efficient for finding content files\n * - tinyglobby is now used instead of fast-glob\n * - tinyglobby provides similar functionality with smaller bundle size\n * - tinyglobby's globSync returns absolute paths when absolute: true is set\n *\n * Front matter parsing:\n * - Uses front-matter library to parse YAML/TOML front matter\n * - Extracts metadata like title, date, tags, author, etc.\n * - Supports both YAML (---) and TOML (+++) delimiters\n * - Returns structured attributes for prerendering\n *\n * File path processing:\n * - Normalizes paths for cross-platform compatibility\n * - Extracts file name without extension\n * - Determines file extension for content type handling\n * - Maintains relative path structure for routing\n */\nexport function getMatchingContentFilesWithFrontMatter(\n workspaceRoot: string,\n rootDir: string,\n glob: string,\n recursive = false,\n): PrerenderContentFile[] {\n // Dynamically require front-matter library\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const fm = require('front-matter');\n\n // Normalize the project root path for consistent path handling\n const root = normalizePath(resolve(workspaceRoot, rootDir));\n\n // Resolve the content directory path relative to the project root\n const resolvedDir = normalizePath(relative(root, join(root, glob)));\n\n // Discover all content files in the specified directory.\n // Default pattern matches only top-level files; recursive opt-in walks subdirectories.\n const pattern = recursive\n ? `${root}/${resolvedDir}/**/*`\n : `${root}/${resolvedDir}/*`;\n const contentFiles: string[] = globSync([pattern], {\n dot: true,\n absolute: true,\n onlyFiles: true,\n });\n\n const dirPrefix = `${root}/${resolvedDir}`;\n\n // Process each discovered content file to extract metadata and front matter\n const mappedFilesWithFm: PrerenderContentFile[] = contentFiles.map((f) => {\n // Read the file contents as UTF-8 text\n const fileContents = readFileSync(f, 'utf8');\n\n // Parse front matter from the file content\n const raw = fm(fileContents);\n\n const filepath = normalizePath(f).replace(root, '');\n\n const match = filepath.match(/\\/([^/.]+)(\\.([^/.]+))?$/);\n let name = '';\n let extension = '';\n if (match) {\n name = match[1]; // File name without extension\n extension = match[3] || ''; // File extension or empty string if no extension\n }\n\n // Path of the file's directory relative to the configured contentDir.\n // For top-level files this is an empty string; for nested files it\n // gives transforms enough context to disambiguate identically-named\n // files (e.g. docs/a/post.md vs docs/b/post.md).\n const relativeDir = normalizePath(relative(dirPrefix, f));\n const lastSlash = relativeDir.lastIndexOf('/');\n const relativePath =\n lastSlash === -1 ? '' : relativeDir.slice(0, lastSlash);\n\n // Return structured content file data for prerendering\n return {\n name,\n extension,\n path: resolvedDir,\n attributes: raw.attributes as { attributes: Record<string, any> },\n content: fileContents,\n relativePath,\n };\n });\n\n return mappedFilesWithFm;\n}\n"],"mappings":";;;;;;AAQA,IAAM,UAAU,cAAc,OAAO,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwD9C,SAAgB,uCACd,eACA,SACA,MACA,YAAY,OACY;CAGxB,MAAM,KAAK,QAAQ,eAAe;CAGlC,MAAM,OAAO,cAAc,QAAQ,eAAe,QAAQ,CAAC;CAG3D,MAAM,cAAc,cAAc,SAAS,MAAM,KAAK,MAAM,KAAK,CAAC,CAAC;CAOnE,MAAM,eAAyB,SAAS,CAHxB,YACZ,GAAG,KAAK,GAAG,YAAY,SACvB,GAAG,KAAK,GAAG,YAAY,IACsB,EAAE;EACjD,KAAK;EACL,UAAU;EACV,WAAW;EACZ,CAAC;CAEF,MAAM,YAAY,GAAG,KAAK,GAAG;AAwC7B,QArCkD,aAAa,KAAK,MAAM;EAExE,MAAM,eAAe,aAAa,GAAG,OAAO;EAG5C,MAAM,MAAM,GAAG,aAAa;EAI5B,MAAM,QAFW,cAAc,EAAE,CAAC,QAAQ,MAAM,GAAG,CAE5B,MAAM,2BAA2B;EACxD,IAAI,OAAO;EACX,IAAI,YAAY;AAChB,MAAI,OAAO;AACT,UAAO,MAAM;AACb,eAAY,MAAM,MAAM;;EAO1B,MAAM,cAAc,cAAc,SAAS,WAAW,EAAE,CAAC;EACzD,MAAM,YAAY,YAAY,YAAY,IAAI;EAC9C,MAAM,eACJ,cAAc,KAAK,KAAK,YAAY,MAAM,GAAG,UAAU;AAGzD,SAAO;GACL;GACA;GACA,MAAM;GACN,YAAY,IAAI;GAChB,SAAS;GACT;GACD;GACD"}
@@ -1,62 +1,59 @@
1
- import type { NitroEventHandler } from 'nitro/types';
1
+ import type { NitroEventHandler } from "nitro/types";
2
2
  type GetHandlersArgs = {
3
- workspaceRoot: string;
4
- sourceRoot: string;
5
- rootDir: string;
6
- additionalPagesDirs?: string[];
7
- hasAPIDir?: boolean;
3
+ workspaceRoot: string;
4
+ sourceRoot: string;
5
+ rootDir: string;
6
+ additionalPagesDirs?: string[];
8
7
  };
9
8
  /**
10
- * Discovers and generates Nitro event handlers for server-side page routes.
11
- *
12
- * This function:
13
- * 1. Discovers all .server.ts files in the app/pages directory and additional pages directories
14
- * 2. Converts file paths to route patterns using Angular-style route syntax
15
- * 3. Generates Nitro event handlers with proper route mapping and lazy loading
16
- * 4. Handles dynamic route parameters and catch-all routes
17
- *
18
- * @param workspaceRoot The workspace root directory path
19
- * @param sourceRoot The source directory path (e.g., 'src')
20
- * @param rootDir The project root directory relative to workspace
21
- * @param additionalPagesDirs Optional array of additional pages directories to scan
22
- * @param hasAPIDir Whether the project has an API directory (affects route prefixing)
23
- * @returns Array of NitroEventHandler objects with handler paths and route patterns
24
- *
25
- * Example usage:
26
- * const handlers = getPageHandlers({
27
- * workspaceRoot: '/workspace',
28
- * sourceRoot: 'src',
29
- * rootDir: 'apps/my-app',
30
- * additionalPagesDirs: ['/libs/shared/pages'],
31
- * hasAPIDir: true
32
- * });
33
- *
34
- * Sample discovered file paths:
35
- * - /workspace/apps/my-app/src/app/pages/index.server.ts
36
- * - /workspace/apps/my-app/src/app/pages/users/[id].server.ts
37
- * - /workspace/apps/my-app/src/app/pages/products/[...slug].server.ts
38
- * - /workspace/apps/my-app/src/app/pages/(auth)/login.server.ts
39
- *
40
- * Route transformation examples:
41
- * - index.server.ts → /_analog/pages/index
42
- * - users/[id].server.ts → /_analog/pages/users/:id
43
- * - products/[...slug].server.ts → /_analog/pages/products/**:slug
44
- * - (auth)/login.server.ts /_analog/pages/-auth-/login
45
- *
46
- * tinyglobby vs fast-glob comparison:
47
- * - Both support the same glob patterns for file discovery
48
- * - Both are efficient for finding server-side page files
49
- * - tinyglobby is now used instead of fast-glob
50
- * - tinyglobby provides similar functionality with smaller bundle size
51
- * - tinyglobby's globSync returns absolute paths when absolute: true is set
52
- *
53
- * Route transformation rules:
54
- * 1. Removes .server.ts extension
55
- * 2. Converts [param] to :param for dynamic routes
56
- * 3. Converts [...param] to **:param for catch-all routes
57
- * 4. Converts (group) to -group- for route groups
58
- * 5. Converts dots to forward slashes
59
- * 6. Prefixes with /_analog/pages and optionally /api
60
- */
61
- export declare function getPageHandlers({ workspaceRoot, sourceRoot, rootDir, additionalPagesDirs, hasAPIDir, }: GetHandlersArgs): NitroEventHandler[];
9
+ * Discovers and generates Nitro event handlers for server-side page routes.
10
+ *
11
+ * This function:
12
+ * 1. Discovers all .server.ts files in the app/pages directory and additional pages directories
13
+ * 2. Converts file paths to route patterns using Angular-style route syntax
14
+ * 3. Generates Nitro event handlers with proper route mapping and lazy loading
15
+ * 4. Handles dynamic route parameters and catch-all routes
16
+ *
17
+ * @param workspaceRoot The workspace root directory path
18
+ * @param sourceRoot The source directory path (e.g., 'src')
19
+ * @param rootDir The project root directory relative to workspace
20
+ * @param additionalPagesDirs Optional array of additional pages directories to scan
21
+ * @returns Array of NitroEventHandler objects with handler paths and route patterns
22
+ *
23
+ * Example usage:
24
+ * const handlers = getPageHandlers({
25
+ * workspaceRoot: '/workspace',
26
+ * sourceRoot: 'src',
27
+ * rootDir: 'apps/my-app',
28
+ * additionalPagesDirs: ['/libs/shared/pages'],
29
+ * });
30
+ *
31
+ * Sample discovered file paths:
32
+ * - /workspace/apps/my-app/src/app/pages/index.server.ts
33
+ * - /workspace/apps/my-app/src/app/pages/users/[id].server.ts
34
+ * - /workspace/apps/my-app/src/app/pages/products/[...slug].server.ts
35
+ * - /workspace/apps/my-app/src/app/pages/(auth)/login.server.ts
36
+ *
37
+ * Route transformation examples:
38
+ * - index.server.ts → /api/_analog/pages/index
39
+ * - users/[id].server.ts → /api/_analog/pages/users/:id
40
+ * - products/[...slug].server.ts → /api/_analog/pages/products/**:slug
41
+ * - (auth)/login.server.ts → /api/_analog/pages/-auth-/login
42
+ *
43
+ * tinyglobby vs fast-glob comparison:
44
+ * - Both support the same glob patterns for file discovery
45
+ * - Both are efficient for finding server-side page files
46
+ * - tinyglobby is now used instead of fast-glob
47
+ * - tinyglobby provides similar functionality with smaller bundle size
48
+ * - tinyglobby's globSync returns absolute paths when absolute: true is set
49
+ *
50
+ * Route transformation rules:
51
+ * 1. Removes .server.ts extension
52
+ * 2. Converts [param] to :param for dynamic routes
53
+ * 3. Converts [...param] to **:param for catch-all routes
54
+ * 4. Converts (group) to -group- for route groups
55
+ * 5. Converts dots to forward slashes
56
+ * 6. Prefixes with /api/_analog/pages
57
+ */
58
+ export declare function getPageHandlers({ workspaceRoot, sourceRoot, rootDir, additionalPagesDirs }: GetHandlersArgs): NitroEventHandler[];
62
59
  export {};
@@ -1,87 +1,70 @@
1
- import { resolve } from 'node:path';
2
- import { globSync } from 'tinyglobby';
3
- import { normalizePath } from 'vite';
1
+ import { normalizePath } from "vite";
2
+ import { resolve } from "node:path";
3
+ import { globSync } from "tinyglobby";
4
+ //#region packages/vite-plugin-nitro/src/lib/utils/get-page-handlers.ts
4
5
  /**
5
- * Discovers and generates Nitro event handlers for server-side page routes.
6
- *
7
- * This function:
8
- * 1. Discovers all .server.ts files in the app/pages directory and additional pages directories
9
- * 2. Converts file paths to route patterns using Angular-style route syntax
10
- * 3. Generates Nitro event handlers with proper route mapping and lazy loading
11
- * 4. Handles dynamic route parameters and catch-all routes
12
- *
13
- * @param workspaceRoot The workspace root directory path
14
- * @param sourceRoot The source directory path (e.g., 'src')
15
- * @param rootDir The project root directory relative to workspace
16
- * @param additionalPagesDirs Optional array of additional pages directories to scan
17
- * @param hasAPIDir Whether the project has an API directory (affects route prefixing)
18
- * @returns Array of NitroEventHandler objects with handler paths and route patterns
19
- *
20
- * Example usage:
21
- * const handlers = getPageHandlers({
22
- * workspaceRoot: '/workspace',
23
- * sourceRoot: 'src',
24
- * rootDir: 'apps/my-app',
25
- * additionalPagesDirs: ['/libs/shared/pages'],
26
- * hasAPIDir: true
27
- * });
28
- *
29
- * Sample discovered file paths:
30
- * - /workspace/apps/my-app/src/app/pages/index.server.ts
31
- * - /workspace/apps/my-app/src/app/pages/users/[id].server.ts
32
- * - /workspace/apps/my-app/src/app/pages/products/[...slug].server.ts
33
- * - /workspace/apps/my-app/src/app/pages/(auth)/login.server.ts
34
- *
35
- * Route transformation examples:
36
- * - index.server.ts → /_analog/pages/index
37
- * - users/[id].server.ts → /_analog/pages/users/:id
38
- * - products/[...slug].server.ts → /_analog/pages/products/**:slug
39
- * - (auth)/login.server.ts /_analog/pages/-auth-/login
40
- *
41
- * tinyglobby vs fast-glob comparison:
42
- * - Both support the same glob patterns for file discovery
43
- * - Both are efficient for finding server-side page files
44
- * - tinyglobby is now used instead of fast-glob
45
- * - tinyglobby provides similar functionality with smaller bundle size
46
- * - tinyglobby's globSync returns absolute paths when absolute: true is set
47
- *
48
- * Route transformation rules:
49
- * 1. Removes .server.ts extension
50
- * 2. Converts [param] to :param for dynamic routes
51
- * 3. Converts [...param] to **:param for catch-all routes
52
- * 4. Converts (group) to -group- for route groups
53
- * 5. Converts dots to forward slashes
54
- * 6. Prefixes with /_analog/pages and optionally /api
55
- */
56
- export function getPageHandlers({ workspaceRoot, sourceRoot, rootDir, additionalPagesDirs, hasAPIDir, }) {
57
- // Normalize the project root path for consistent path handling
58
- const root = normalizePath(resolve(workspaceRoot, rootDir));
59
- // Discover all .server.ts files in the app/pages directory and additional pages directories
60
- // Pattern: looks for any .server.ts files in app/pages/**/*.server.ts and additional directories
61
- const endpointFiles = globSync([
62
- `${root}/${sourceRoot}/app/pages/**/*.server.ts`,
63
- ...(additionalPagesDirs || []).map((dir) => `${workspaceRoot}${dir}/**/*.server.ts`),
64
- ], { dot: true, absolute: true });
65
- // Transform each discovered file into a Nitro event handler
66
- const handlers = endpointFiles.map((endpointFile) => {
67
- // Normalize the endpoint file path for consistent path handling
68
- const normalized = normalizePath(endpointFile);
69
- // Transform the normalized path into a route pattern
70
- const route = normalized
71
- .replace(/^(.*?)\/pages/, '/pages')
72
- .replace(/\.server\.ts$/, '') // Remove .server.ts extension
73
- .replace(/\[\.{3}(.+)\]/g, '**:$1') // Convert [...param] to **:param (catch-all routes)
74
- .replace(/\[\.{3}(\w+)\]/g, '**:$1') // Alternative catch-all pattern
75
- .replace(/\/\((.*?)\)$/, '/-$1-') // Convert (group) to -group- (route groups)
76
- .replace(/\[(\w+)\]/g, ':$1') // Convert [param] to :param (dynamic routes)
77
- .replace(/\./g, '/'); // Convert dots to forward slashes
78
- // Return Nitro event handler with absolute handler path and transformed route
79
- return {
80
- handler: endpointFile,
81
- route: `${hasAPIDir ? '/api' : ''}/_analog${route}`,
82
- lazy: true,
83
- };
84
- });
85
- return handlers;
6
+ * Discovers and generates Nitro event handlers for server-side page routes.
7
+ *
8
+ * This function:
9
+ * 1. Discovers all .server.ts files in the app/pages directory and additional pages directories
10
+ * 2. Converts file paths to route patterns using Angular-style route syntax
11
+ * 3. Generates Nitro event handlers with proper route mapping and lazy loading
12
+ * 4. Handles dynamic route parameters and catch-all routes
13
+ *
14
+ * @param workspaceRoot The workspace root directory path
15
+ * @param sourceRoot The source directory path (e.g., 'src')
16
+ * @param rootDir The project root directory relative to workspace
17
+ * @param additionalPagesDirs Optional array of additional pages directories to scan
18
+ * @returns Array of NitroEventHandler objects with handler paths and route patterns
19
+ *
20
+ * Example usage:
21
+ * const handlers = getPageHandlers({
22
+ * workspaceRoot: '/workspace',
23
+ * sourceRoot: 'src',
24
+ * rootDir: 'apps/my-app',
25
+ * additionalPagesDirs: ['/libs/shared/pages'],
26
+ * });
27
+ *
28
+ * Sample discovered file paths:
29
+ * - /workspace/apps/my-app/src/app/pages/index.server.ts
30
+ * - /workspace/apps/my-app/src/app/pages/users/[id].server.ts
31
+ * - /workspace/apps/my-app/src/app/pages/products/[...slug].server.ts
32
+ * - /workspace/apps/my-app/src/app/pages/(auth)/login.server.ts
33
+ *
34
+ * Route transformation examples:
35
+ * - index.server.ts → /api/_analog/pages/index
36
+ * - users/[id].server.ts → /api/_analog/pages/users/:id
37
+ * - products/[...slug].server.ts → /api/_analog/pages/products/**:slug
38
+ * - (auth)/login.server.ts → /api/_analog/pages/-auth-/login
39
+ *
40
+ * tinyglobby vs fast-glob comparison:
41
+ * - Both support the same glob patterns for file discovery
42
+ * - Both are efficient for finding server-side page files
43
+ * - tinyglobby is now used instead of fast-glob
44
+ * - tinyglobby provides similar functionality with smaller bundle size
45
+ * - tinyglobby's globSync returns absolute paths when absolute: true is set
46
+ *
47
+ * Route transformation rules:
48
+ * 1. Removes .server.ts extension
49
+ * 2. Converts [param] to :param for dynamic routes
50
+ * 3. Converts [...param] to **:param for catch-all routes
51
+ * 4. Converts (group) to -group- for route groups
52
+ * 5. Converts dots to forward slashes
53
+ * 6. Prefixes with /api/_analog/pages
54
+ */
55
+ function getPageHandlers({ workspaceRoot, sourceRoot, rootDir, additionalPagesDirs }) {
56
+ return globSync([`${normalizePath(resolve(workspaceRoot, rootDir))}/${sourceRoot}/app/pages/**/*.server.ts`, ...(additionalPagesDirs || []).map((dir) => `${workspaceRoot}${dir}/**/*.server.ts`)], {
57
+ dot: true,
58
+ absolute: true
59
+ }).map((endpointFile) => {
60
+ return {
61
+ handler: endpointFile,
62
+ route: `/api/_analog${normalizePath(endpointFile).replace(/^(.*?)\/pages/, "/pages").replace(/\.server\.ts$/, "").replace(/\[\.{3}(.+)\]/g, "**:$1").replace(/\[\.{3}(\w+)\]/g, "**:$1").replace(/\/\((.*?)\)$/, "/-$1-").replace(/\[(\w+)\]/g, ":$1").replace(/\./g, "/")}`,
63
+ lazy: true
64
+ };
65
+ });
86
66
  }
67
+ //#endregion
68
+ export { getPageHandlers };
69
+
87
70
  //# sourceMappingURL=get-page-handlers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"get-page-handlers.js","sourceRoot":"","sources":["../../../../../../packages/vite-plugin-nitro/src/lib/utils/get-page-handlers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAY,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,OAAO,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAUrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,aAAa,EACb,UAAU,EACV,OAAO,EACP,mBAAmB,EACnB,SAAS,GACO;IAChB,+DAA+D;IAC/D,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC;IAE5D,4FAA4F;IAC5F,iGAAiG;IACjG,MAAM,aAAa,GAAa,QAAQ,CACtC;QACE,GAAG,IAAI,IAAI,UAAU,2BAA2B;QAChD,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC,GAAG,CAChC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,aAAa,GAAG,GAAG,iBAAiB,CACjD;KACF,EACD,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAC9B,CAAC;IAEF,4DAA4D;IAC5D,MAAM,QAAQ,GAAwB,aAAa,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE;QACvE,gEAAgE;QAChE,MAAM,UAAU,GAAG,aAAa,CAAC,YAAY,CAAC,CAAC;QAC/C,qDAAqD;QACrD,MAAM,KAAK,GAAG,UAAU;aACrB,OAAO,CAAC,eAAe,EAAE,QAAQ,CAAC;aAClC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,8BAA8B;aAC3D,OAAO,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC,oDAAoD;aACvF,OAAO,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC,gCAAgC;aACpE,OAAO,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,4CAA4C;aAC7E,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,6CAA6C;aAC1E,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,kCAAkC;QAE1D,8EAA8E;QAC9E,OAAO;YACL,OAAO,EAAE,YAAY;YACrB,KAAK,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,KAAK,EAAE;YACnD,IAAI,EAAE,IAAI;SACX,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"get-page-handlers.js","names":[],"sources":["../../../../src/lib/utils/get-page-handlers.ts"],"sourcesContent":["import { resolve, relative } from 'node:path';\nimport { globSync } from 'tinyglobby';\n\nimport type { NitroEventHandler } from 'nitro/types';\nimport { normalizePath } from 'vite';\n\ntype GetHandlersArgs = {\n workspaceRoot: string;\n sourceRoot: string;\n rootDir: string;\n additionalPagesDirs?: string[];\n};\n\n/**\n * Discovers and generates Nitro event handlers for server-side page routes.\n *\n * This function:\n * 1. Discovers all .server.ts files in the app/pages directory and additional pages directories\n * 2. Converts file paths to route patterns using Angular-style route syntax\n * 3. Generates Nitro event handlers with proper route mapping and lazy loading\n * 4. Handles dynamic route parameters and catch-all routes\n *\n * @param workspaceRoot The workspace root directory path\n * @param sourceRoot The source directory path (e.g., 'src')\n * @param rootDir The project root directory relative to workspace\n * @param additionalPagesDirs Optional array of additional pages directories to scan\n * @returns Array of NitroEventHandler objects with handler paths and route patterns\n *\n * Example usage:\n * const handlers = getPageHandlers({\n * workspaceRoot: '/workspace',\n * sourceRoot: 'src',\n * rootDir: 'apps/my-app',\n * additionalPagesDirs: ['/libs/shared/pages'],\n * });\n *\n * Sample discovered file paths:\n * - /workspace/apps/my-app/src/app/pages/index.server.ts\n * - /workspace/apps/my-app/src/app/pages/users/[id].server.ts\n * - /workspace/apps/my-app/src/app/pages/products/[...slug].server.ts\n * - /workspace/apps/my-app/src/app/pages/(auth)/login.server.ts\n *\n * Route transformation examples:\n * - index.server.ts → /api/_analog/pages/index\n * - users/[id].server.ts → /api/_analog/pages/users/:id\n * - products/[...slug].server.ts → /api/_analog/pages/products/**:slug\n * - (auth)/login.server.ts → /api/_analog/pages/-auth-/login\n *\n * tinyglobby vs fast-glob comparison:\n * - Both support the same glob patterns for file discovery\n * - Both are efficient for finding server-side page files\n * - tinyglobby is now used instead of fast-glob\n * - tinyglobby provides similar functionality with smaller bundle size\n * - tinyglobby's globSync returns absolute paths when absolute: true is set\n *\n * Route transformation rules:\n * 1. Removes .server.ts extension\n * 2. Converts [param] to :param for dynamic routes\n * 3. Converts [...param] to **:param for catch-all routes\n * 4. Converts (group) to -group- for route groups\n * 5. Converts dots to forward slashes\n * 6. Prefixes with /api/_analog/pages\n */\nexport function getPageHandlers({\n workspaceRoot,\n sourceRoot,\n rootDir,\n additionalPagesDirs,\n}: GetHandlersArgs): NitroEventHandler[] {\n // Normalize the project root path for consistent path handling\n const root = normalizePath(resolve(workspaceRoot, rootDir));\n\n // Discover all .server.ts files in the app/pages directory and additional pages directories\n // Pattern: looks for any .server.ts files in app/pages/**/*.server.ts and additional directories\n const endpointFiles: string[] = globSync(\n [\n `${root}/${sourceRoot}/app/pages/**/*.server.ts`,\n ...(additionalPagesDirs || []).map(\n (dir) => `${workspaceRoot}${dir}/**/*.server.ts`,\n ),\n ],\n { dot: true, absolute: true },\n );\n\n // Transform each discovered file into a Nitro event handler\n const handlers: NitroEventHandler[] = endpointFiles.map((endpointFile) => {\n // Normalize the endpoint file path for consistent path handling\n const normalized = normalizePath(endpointFile);\n // Transform the normalized path into a route pattern\n const route = normalized\n .replace(/^(.*?)\\/pages/, '/pages')\n .replace(/\\.server\\.ts$/, '') // Remove .server.ts extension\n .replace(/\\[\\.{3}(.+)\\]/g, '**:$1') // Convert [...param] to **:param (catch-all routes)\n .replace(/\\[\\.{3}(\\w+)\\]/g, '**:$1') // Alternative catch-all pattern\n .replace(/\\/\\((.*?)\\)$/, '/-$1-') // Convert (group) to -group- (route groups)\n .replace(/\\[(\\w+)\\]/g, ':$1') // Convert [param] to :param (dynamic routes)\n .replace(/\\./g, '/'); // Convert dots to forward slashes\n\n // Return Nitro event handler with absolute handler path and transformed route\n return {\n handler: endpointFile,\n route: `/api/_analog${route}`,\n lazy: true,\n };\n });\n\n return handlers;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,SAAgB,gBAAgB,EAC9B,eACA,YACA,SACA,uBACuC;AAsCvC,QAhCgC,SAC9B,CACE,GANS,cAAc,QAAQ,eAAe,QAAQ,CAAC,CAM/C,GAAG,WAAW,4BACtB,IAAI,uBAAuB,EAAE,EAAE,KAC5B,QAAQ,GAAG,gBAAgB,IAAI,iBACjC,CACF,EACD;EAAE,KAAK;EAAM,UAAU;EAAM,CAC9B,CAGmD,KAAK,iBAAiB;AAcxE,SAAO;GACL,SAAS;GACT,OAAO,eAdU,cAAc,aAAa,CAG3C,QAAQ,iBAAiB,SAAS,CAClC,QAAQ,iBAAiB,GAAG,CAC5B,QAAQ,kBAAkB,QAAQ,CAClC,QAAQ,mBAAmB,QAAQ,CACnC,QAAQ,gBAAgB,QAAQ,CAChC,QAAQ,cAAc,MAAM,CAC5B,QAAQ,OAAO,IAAI;GAMpB,MAAM;GACP;GACD"}
@@ -0,0 +1,28 @@
1
+ export type GetServerFnHandlersArgs = {
2
+ workspaceRoot: string;
3
+ sourceRoot: string;
4
+ rootDir: string;
5
+ additionalServerFnDirs?: string[];
6
+ };
7
+ export type ServerFnHandlerModule = {
8
+ /** Absolute, normalized path to a discovered `*.server.ts` module. */
9
+ file: string;
10
+ };
11
+ /**
12
+ * Discovers the `*.server.ts` modules that may define server functions.
13
+ *
14
+ * Unlike page endpoints (one Nitro handler per file), server functions all
15
+ * share a single `/_analog/fn/:id` dispatch route. The discovered modules are
16
+ * imported for their registration side-effects — each `serverFn(...)` call
17
+ * registers itself into the server-side registry at import time — after which
18
+ * dispatch looks up the requested function by id.
19
+ *
20
+ * Scope is `<projectRoot>/<sourceRoot>/**\/*.server.ts` because the RFC allows a
21
+ * server function to live in any `.server.ts` module, including existing page
22
+ * server files. Files that define no server function simply register nothing.
23
+ * Angular SSR config (`app.config.server.ts`) is excluded — it is not a route or
24
+ * function module and must not be pulled into the dispatch bundle.
25
+ *
26
+ * @returns discovered modules, de-duplicated and sorted for deterministic output
27
+ */
28
+ export declare function getServerFnHandlers({ workspaceRoot, sourceRoot, rootDir, additionalServerFnDirs }: GetServerFnHandlersArgs): ServerFnHandlerModule[];
@@ -0,0 +1,45 @@
1
+ import { normalizePath } from "vite";
2
+ import { resolve } from "node:path";
3
+ import { globSync } from "tinyglobby";
4
+ //#region packages/vite-plugin-nitro/src/lib/utils/get-server-fn-handlers.ts
5
+ /**
6
+ * `*.server.ts` files that are matched by the glob but are never server-function
7
+ * hosts, so importing them for registration side-effects would be wrong.
8
+ *
9
+ * The SSR entries (`main.server.ts`, `main-cf.server.ts`, …) sit at the top of
10
+ * the source root and bootstrap the whole Angular application; importing one
11
+ * would pull the entire app into the dispatch bundle. They are matched by
12
+ * directory rather than by name so a page named `main.server.ts` still counts.
13
+ */
14
+ var EXCLUDED_SERVER_FILES = [/\/app\.config\.server\.ts$/];
15
+ /**
16
+ * Discovers the `*.server.ts` modules that may define server functions.
17
+ *
18
+ * Unlike page endpoints (one Nitro handler per file), server functions all
19
+ * share a single `/_analog/fn/:id` dispatch route. The discovered modules are
20
+ * imported for their registration side-effects — each `serverFn(...)` call
21
+ * registers itself into the server-side registry at import time — after which
22
+ * dispatch looks up the requested function by id.
23
+ *
24
+ * Scope is `<projectRoot>/<sourceRoot>/**\/*.server.ts` because the RFC allows a
25
+ * server function to live in any `.server.ts` module, including existing page
26
+ * server files. Files that define no server function simply register nothing.
27
+ * Angular SSR config (`app.config.server.ts`) is excluded — it is not a route or
28
+ * function module and must not be pulled into the dispatch bundle.
29
+ *
30
+ * @returns discovered modules, de-duplicated and sorted for deterministic output
31
+ */
32
+ function getServerFnHandlers({ workspaceRoot, sourceRoot, rootDir, additionalServerFnDirs }) {
33
+ const root = normalizePath(resolve(workspaceRoot, rootDir));
34
+ const files = globSync([`${root}/${sourceRoot}/**/*.server.ts`, ...(additionalServerFnDirs || []).map((dir) => `${workspaceRoot}${dir}/**/*.server.ts`)], {
35
+ dot: true,
36
+ absolute: true
37
+ }).map((file) => normalizePath(file));
38
+ const sourceRootDir = `${root}/${sourceRoot}/`;
39
+ const seen = /* @__PURE__ */ new Set();
40
+ return files.filter((file) => !EXCLUDED_SERVER_FILES.some((re) => re.test(file))).filter((file) => !file.startsWith(sourceRootDir) || file.slice(sourceRootDir.length).includes("/")).filter((file) => seen.has(file) ? false : (seen.add(file), true)).sort().map((file) => ({ file }));
41
+ }
42
+ //#endregion
43
+ export { getServerFnHandlers };
44
+
45
+ //# sourceMappingURL=get-server-fn-handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-server-fn-handlers.js","names":[],"sources":["../../../../src/lib/utils/get-server-fn-handlers.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { globSync } from 'tinyglobby';\n\nimport { normalizePath } from 'vite';\n\nexport type GetServerFnHandlersArgs = {\n workspaceRoot: string;\n sourceRoot: string;\n rootDir: string;\n additionalServerFnDirs?: string[];\n};\n\nexport type ServerFnHandlerModule = {\n /** Absolute, normalized path to a discovered `*.server.ts` module. */\n file: string;\n};\n\n/**\n * `*.server.ts` files that are matched by the glob but are never server-function\n * hosts, so importing them for registration side-effects would be wrong.\n *\n * The SSR entries (`main.server.ts`, `main-cf.server.ts`, …) sit at the top of\n * the source root and bootstrap the whole Angular application; importing one\n * would pull the entire app into the dispatch bundle. They are matched by\n * directory rather than by name so a page named `main.server.ts` still counts.\n */\nconst EXCLUDED_SERVER_FILES: RegExp[] = [/\\/app\\.config\\.server\\.ts$/];\n\n/**\n * Discovers the `*.server.ts` modules that may define server functions.\n *\n * Unlike page endpoints (one Nitro handler per file), server functions all\n * share a single `/_analog/fn/:id` dispatch route. The discovered modules are\n * imported for their registration side-effects — each `serverFn(...)` call\n * registers itself into the server-side registry at import time — after which\n * dispatch looks up the requested function by id.\n *\n * Scope is `<projectRoot>/<sourceRoot>/**\\/*.server.ts` because the RFC allows a\n * server function to live in any `.server.ts` module, including existing page\n * server files. Files that define no server function simply register nothing.\n * Angular SSR config (`app.config.server.ts`) is excluded — it is not a route or\n * function module and must not be pulled into the dispatch bundle.\n *\n * @returns discovered modules, de-duplicated and sorted for deterministic output\n */\nexport function getServerFnHandlers({\n workspaceRoot,\n sourceRoot,\n rootDir,\n additionalServerFnDirs,\n}: GetServerFnHandlersArgs): ServerFnHandlerModule[] {\n const root = normalizePath(resolve(workspaceRoot, rootDir));\n\n const files = globSync(\n [\n `${root}/${sourceRoot}/**/*.server.ts`,\n ...(additionalServerFnDirs || []).map(\n (dir) => `${workspaceRoot}${dir}/**/*.server.ts`,\n ),\n ],\n { dot: true, absolute: true },\n ).map((file) => normalizePath(file));\n\n const sourceRootDir = `${root}/${sourceRoot}/`;\n const seen = new Set<string>();\n return files\n .filter((file) => !EXCLUDED_SERVER_FILES.some((re) => re.test(file)))\n .filter(\n (file) =>\n !file.startsWith(sourceRootDir) ||\n file.slice(sourceRootDir.length).includes('/'),\n )\n .filter((file) => (seen.has(file) ? false : (seen.add(file), true)))\n .sort()\n .map((file) => ({ file }));\n}\n"],"mappings":";;;;;;;;;;;;;AA0BA,IAAM,wBAAkC,CAAC,6BAA6B;;;;;;;;;;;;;;;;;;AAmBtE,SAAgB,oBAAoB,EAClC,eACA,YACA,SACA,0BACmD;CACnD,MAAM,OAAO,cAAc,QAAQ,eAAe,QAAQ,CAAC;CAE3D,MAAM,QAAQ,SACZ,CACE,GAAG,KAAK,GAAG,WAAW,kBACtB,IAAI,0BAA0B,EAAE,EAAE,KAC/B,QAAQ,GAAG,gBAAgB,IAAI,iBACjC,CACF,EACD;EAAE,KAAK;EAAM,UAAU;EAAM,CAC9B,CAAC,KAAK,SAAS,cAAc,KAAK,CAAC;CAEpC,MAAM,gBAAgB,GAAG,KAAK,GAAG,WAAW;CAC5C,MAAM,uBAAO,IAAI,KAAa;AAC9B,QAAO,MACJ,QAAQ,SAAS,CAAC,sBAAsB,MAAM,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CACpE,QACE,SACC,CAAC,KAAK,WAAW,cAAc,IAC/B,KAAK,MAAM,cAAc,OAAO,CAAC,SAAS,IAAI,CACjD,CACA,QAAQ,SAAU,KAAK,IAAI,KAAK,GAAG,SAAS,KAAK,IAAI,KAAK,EAAE,MAAO,CACnE,MAAM,CACN,KAAK,UAAU,EAAE,MAAM,EAAE"}
@@ -0,0 +1,36 @@
1
+ import { PrerenderRoute } from "nitropack";
2
+ import { I18nPrerenderOptions } from "../options.js";
3
+ /**
4
+ * Expands a list of routes to include locale-prefixed variants.
5
+ *
6
+ * For each route and each locale, generates a prefixed route:
7
+ * '/' + locale + route
8
+ *
9
+ * The default locale's routes are included both with and without the prefix
10
+ * so that `/about` and `/en/about` both render.
11
+ *
12
+ * @param routes - The original routes to expand
13
+ * @param i18n - The i18n prerender configuration
14
+ * @returns Expanded routes with locale prefixes
15
+ */
16
+ export declare function expandRoutesWithLocales(routes: string[], i18n: I18nPrerenderOptions): string[];
17
+ /**
18
+ * Creates a post-rendering hook that injects the `lang` attribute
19
+ * into the `<html>` tag of prerendered pages based on the route's
20
+ * locale prefix.
21
+ *
22
+ * @param i18n - The i18n prerender configuration
23
+ * @returns A post-rendering hook function
24
+ */
25
+ export declare function createI18nPostRenderingHook(i18n: I18nPrerenderOptions): (route: PrerenderRoute) => Promise<void>;
26
+ /**
27
+ * Detects the locale from a prerendered route path by checking
28
+ * the first path segment against the configured locales.
29
+ */
30
+ export declare function detectLocaleFromRoute(route: string, i18n: I18nPrerenderOptions): string;
31
+ /**
32
+ * Sets the `lang` attribute on the `<html>` tag in an HTML string.
33
+ * If a `lang` attribute already exists, it is replaced.
34
+ * If no `lang` attribute exists, it is added.
35
+ */
36
+ export declare function setHtmlLang(html: string, locale: string): string;
@@ -0,0 +1,23 @@
1
+ //#region packages/vite-plugin-nitro/src/lib/utils/i18n-prerender.ts
2
+ /**
3
+ * Detects the locale from a prerendered route path by checking
4
+ * the first path segment against the configured locales.
5
+ */
6
+ function detectLocaleFromRoute(route, i18n) {
7
+ const firstSegment = route.split("/").filter(Boolean)[0];
8
+ if (firstSegment && i18n.locales.includes(firstSegment)) return firstSegment;
9
+ return i18n.defaultLocale;
10
+ }
11
+ /**
12
+ * Sets the `lang` attribute on the `<html>` tag in an HTML string.
13
+ * If a `lang` attribute already exists, it is replaced.
14
+ * If no `lang` attribute exists, it is added.
15
+ */
16
+ function setHtmlLang(html, locale) {
17
+ if (/<html[^>]*\slang\s*=\s*["'][^"']*["']/i.test(html)) return html.replace(/(<html[^>]*\s)lang\s*=\s*["'][^"']*["']/i, `$1lang="${locale}"`);
18
+ return html.replace(/<html/i, `<html lang="${locale}"`);
19
+ }
20
+ //#endregion
21
+ export { detectLocaleFromRoute, setHtmlLang };
22
+
23
+ //# sourceMappingURL=i18n-prerender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n-prerender.js","names":[],"sources":["../../../../src/lib/utils/i18n-prerender.ts"],"sourcesContent":["import { PrerenderRoute } from 'nitropack';\nimport { I18nPrerenderOptions } from '../options.js';\n\n/**\n * Expands a list of routes to include locale-prefixed variants.\n *\n * For each route and each locale, generates a prefixed route:\n * '/' + locale + route\n *\n * The default locale's routes are included both with and without the prefix\n * so that `/about` and `/en/about` both render.\n *\n * @param routes - The original routes to expand\n * @param i18n - The i18n prerender configuration\n * @returns Expanded routes with locale prefixes\n */\nexport function expandRoutesWithLocales(\n routes: string[],\n i18n: I18nPrerenderOptions,\n): string[] {\n const expanded: string[] = [];\n\n for (const route of routes) {\n // Skip API routes — they don't need locale prefixes\n if (route.includes('/_analog/') || route.startsWith('/api/')) {\n expanded.push(route);\n continue;\n }\n\n for (const locale of i18n.locales) {\n const prefix = `/${locale}`;\n const localizedRoute = route === '/' ? prefix : `${prefix}${route}`;\n expanded.push(localizedRoute);\n }\n\n // Keep the unprefixed route for the default locale\n if (!expanded.includes(route)) {\n expanded.push(route);\n }\n }\n\n return expanded;\n}\n\n/**\n * Creates a post-rendering hook that injects the `lang` attribute\n * into the `<html>` tag of prerendered pages based on the route's\n * locale prefix.\n *\n * @param i18n - The i18n prerender configuration\n * @returns A post-rendering hook function\n */\nexport function createI18nPostRenderingHook(\n i18n: I18nPrerenderOptions,\n): (route: PrerenderRoute) => Promise<void> {\n return async (route: PrerenderRoute) => {\n if (!route.contents || typeof route.contents !== 'string') {\n return;\n }\n\n const locale = detectLocaleFromRoute(route.route, i18n);\n if (!locale) {\n return;\n }\n\n // Inject or replace the lang attribute on <html>\n route.contents = setHtmlLang(route.contents, locale);\n };\n}\n\n/**\n * Detects the locale from a prerendered route path by checking\n * the first path segment against the configured locales.\n */\nexport function detectLocaleFromRoute(\n route: string,\n i18n: I18nPrerenderOptions,\n): string {\n const segments = route.split('/').filter(Boolean);\n const firstSegment = segments[0];\n\n if (firstSegment && i18n.locales.includes(firstSegment)) {\n return firstSegment;\n }\n\n return i18n.defaultLocale;\n}\n\n/**\n * Sets the `lang` attribute on the `<html>` tag in an HTML string.\n * If a `lang` attribute already exists, it is replaced.\n * If no `lang` attribute exists, it is added.\n */\nexport function setHtmlLang(html: string, locale: string): string {\n // Replace existing lang attribute\n if (/<html[^>]*\\slang\\s*=\\s*[\"'][^\"']*[\"']/i.test(html)) {\n return html.replace(\n /(<html[^>]*\\s)lang\\s*=\\s*[\"'][^\"']*[\"']/i,\n `$1lang=\"${locale}\"`,\n );\n }\n\n // Add lang attribute to <html> tag\n return html.replace(/<html/i, `<html lang=\"${locale}\"`);\n}\n"],"mappings":";;;;;AA0EA,SAAgB,sBACd,OACA,MACQ;CAER,MAAM,eADW,MAAM,MAAM,IAAI,CAAC,OAAO,QAAQ,CACnB;AAE9B,KAAI,gBAAgB,KAAK,QAAQ,SAAS,aAAa,CACrD,QAAO;AAGT,QAAO,KAAK;;;;;;;AAQd,SAAgB,YAAY,MAAc,QAAwB;AAEhE,KAAI,yCAAyC,KAAK,KAAK,CACrD,QAAO,KAAK,QACV,4CACA,WAAW,OAAO,GACnB;AAIH,QAAO,KAAK,QAAQ,UAAU,eAAe,OAAO,GAAG"}
@@ -0,0 +1,17 @@
1
+ export interface InjectServerFnIdsResult {
2
+ code: string;
3
+ ids: {
4
+ name: string;
5
+ id: string;
6
+ }[];
7
+ }
8
+ /**
9
+ * Server/SSR-build transform: injects the derived `id` into each
10
+ * `export const NAME = serverFn(config, handler)` so the function registers
11
+ * under the same opaque id the client proxy dispatches to. Unlike the client
12
+ * scrub this keeps the handler and every other statement intact — it only edits
13
+ * the config object — so the server module still runs the real implementation.
14
+ *
15
+ * Returns `null` when the module defines no server function.
16
+ */
17
+ export declare function injectServerFnIds(code: string, fileId: string): InjectServerFnIdsResult | null;
@@ -0,0 +1,94 @@
1
+ import { deriveServerFnId } from "./derive-server-fn-id.js";
2
+ import { parseSync } from "oxc-parser";
3
+ import MagicString from "magic-string";
4
+ //#region packages/vite-plugin-nitro/src/lib/utils/inject-server-fn-ids.ts
5
+ var SERVER_FN_SOURCE = "@analogjs/router/server";
6
+ /**
7
+ * Server/SSR-build transform: injects the derived `id` into each
8
+ * `export const NAME = serverFn(config, handler)` so the function registers
9
+ * under the same opaque id the client proxy dispatches to. Unlike the client
10
+ * scrub this keeps the handler and every other statement intact — it only edits
11
+ * the config object — so the server module still runs the real implementation.
12
+ *
13
+ * Returns `null` when the module defines no server function.
14
+ */
15
+ function injectServerFnIds(code, fileId) {
16
+ if (!code.includes("serverFn")) return null;
17
+ const { program } = parseSync(fileId, code);
18
+ const body = program.body;
19
+ const serverFnLocalNames = collectServerFnLocalNames(body);
20
+ const magic = new MagicString(code);
21
+ const ids = [];
22
+ for (const node of body) {
23
+ if (node.type !== "ExportNamedDeclaration" || node.declaration?.type !== "VariableDeclaration") continue;
24
+ for (const declarator of node.declaration.declarations) {
25
+ if (declarator.id?.type !== "Identifier" || declarator.init?.type !== "CallExpression" || declarator.init.callee?.type !== "Identifier" || !serverFnLocalNames.has(declarator.init.callee.name)) continue;
26
+ const name = declarator.id.name;
27
+ const id = deriveServerFnId(fileId, name);
28
+ ids.push({
29
+ name,
30
+ id
31
+ });
32
+ const args = declarator.init.arguments ?? [];
33
+ const arg0 = args[0];
34
+ const idProp = `id: ${JSON.stringify(id)}`;
35
+ if (arg0?.type === "ObjectExpression") {
36
+ assertMethodInputCompatible(arg0, name, fileId);
37
+ injectId(magic, arg0, id);
38
+ } else if (isFunctionNode(arg0)) magic.appendLeft(arg0.start, `{ ${idProp} }, `);
39
+ else if (arg0 && args.length >= 2) {
40
+ magic.appendLeft(arg0.start, `{ ${idProp}, input: `);
41
+ magic.appendRight(arg0.end, ` }`);
42
+ } else throw new Error(`[analog] serverFn "${name}" in ${fileId} must be called as serverFn(handler), serverFn(schema, handler), or serverFn(config, handler).`);
43
+ }
44
+ }
45
+ if (ids.length === 0) return null;
46
+ return {
47
+ code: magic.toString(),
48
+ ids
49
+ };
50
+ }
51
+ /**
52
+ * Insert (or replace) `id: "<derived>"` in the config object. Authors do not
53
+ * supply an id; a stray one is overwritten so the route is always the derived,
54
+ * non-enumerable digest — never an author-controlled value.
55
+ */
56
+ function injectId(magic, configArg, id) {
57
+ const existing = (configArg.properties ?? []).find((prop) => prop.type === "Property" && (prop.key?.type === "Identifier" ? prop.key.name : prop.key?.value) === "id");
58
+ if (existing) {
59
+ magic.overwrite(existing.value.start, existing.value.end, JSON.stringify(id));
60
+ return;
61
+ }
62
+ magic.appendRight(configArg.start + 1, ` id: ${JSON.stringify(id)},`);
63
+ }
64
+ /**
65
+ * Reject `method: 'GET'` alongside an `input` schema at build time: GET carries
66
+ * no body, so a GET function can never receive validated input.
67
+ */
68
+ function assertMethodInputCompatible(configArg, name, fileId) {
69
+ let method;
70
+ let hasInput = false;
71
+ for (const prop of configArg.properties ?? []) {
72
+ if (prop.type !== "Property") continue;
73
+ const key = prop.key?.type === "Identifier" ? prop.key.name : prop.key?.value;
74
+ if (key === "method" && prop.value?.type === "Literal") method = prop.value.value;
75
+ else if (key === "input") hasInput = true;
76
+ }
77
+ if (method === "GET" && hasInput) throw new Error(`[analog] serverFn "${name}" in ${fileId} declares method: 'GET' with an input schema; GET carries no body. Use POST (the default when input is present) or drop the input.`);
78
+ }
79
+ function isFunctionNode(node) {
80
+ return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
81
+ }
82
+ function collectServerFnLocalNames(body) {
83
+ const names = /* @__PURE__ */ new Set();
84
+ for (const node of body) {
85
+ if (node.type !== "ImportDeclaration" || node.source?.value !== SERVER_FN_SOURCE) continue;
86
+ for (const spec of node.specifiers ?? []) if (spec.type === "ImportSpecifier" && spec.imported?.name === "serverFn") names.add(spec.local.name);
87
+ }
88
+ if (names.size === 0) names.add("serverFn");
89
+ return names;
90
+ }
91
+ //#endregion
92
+ export { injectServerFnIds };
93
+
94
+ //# sourceMappingURL=inject-server-fn-ids.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject-server-fn-ids.js","names":[],"sources":["../../../../src/lib/utils/inject-server-fn-ids.ts"],"sourcesContent":["import MagicString from 'magic-string';\nimport { parseSync } from 'oxc-parser';\n\nimport { deriveServerFnId } from './derive-server-fn-id.js';\n\nconst SERVER_FN_SOURCE = '@analogjs/router/server';\n\nexport interface InjectServerFnIdsResult {\n code: string;\n ids: { name: string; id: string }[];\n}\n\n/**\n * Server/SSR-build transform: injects the derived `id` into each\n * `export const NAME = serverFn(config, handler)` so the function registers\n * under the same opaque id the client proxy dispatches to. Unlike the client\n * scrub this keeps the handler and every other statement intact — it only edits\n * the config object — so the server module still runs the real implementation.\n *\n * Returns `null` when the module defines no server function.\n */\nexport function injectServerFnIds(\n code: string,\n fileId: string,\n): InjectServerFnIdsResult | null {\n if (!code.includes('serverFn')) {\n return null;\n }\n\n const { program } = parseSync(fileId, code);\n const body = (program as { body: any[] }).body;\n const serverFnLocalNames = collectServerFnLocalNames(body);\n\n const magic = new MagicString(code);\n const ids: { name: string; id: string }[] = [];\n\n for (const node of body) {\n if (\n node.type !== 'ExportNamedDeclaration' ||\n node.declaration?.type !== 'VariableDeclaration'\n ) {\n continue;\n }\n for (const declarator of node.declaration.declarations) {\n if (\n declarator.id?.type !== 'Identifier' ||\n declarator.init?.type !== 'CallExpression' ||\n declarator.init.callee?.type !== 'Identifier' ||\n !serverFnLocalNames.has(declarator.init.callee.name)\n ) {\n continue;\n }\n\n const name = declarator.id.name;\n const id = deriveServerFnId(fileId, name);\n ids.push({ name, id });\n\n const args = declarator.init.arguments ?? [];\n const arg0 = args[0];\n const idProp = `id: ${JSON.stringify(id)}`;\n\n if (arg0?.type === 'ObjectExpression') {\n // serverFn({ … }, handler) — inject the id into the config object.\n assertMethodInputCompatible(arg0, name, fileId);\n injectId(magic, arg0, id);\n } else if (isFunctionNode(arg0)) {\n // serverFn(handler) — synthesize a config: `{ id }, handler`.\n magic.appendLeft(arg0.start, `{ ${idProp} }, `);\n } else if (arg0 && args.length >= 2) {\n // serverFn(schema, handler) — wrap the schema into `{ id, input: <schema> }`.\n magic.appendLeft(arg0.start, `{ ${idProp}, input: `);\n magic.appendRight(arg0.end, ` }`);\n } else {\n throw new Error(\n `[analog] serverFn \"${name}\" in ${fileId} must be called as serverFn(handler), serverFn(schema, handler), or serverFn(config, handler).`,\n );\n }\n }\n }\n\n if (ids.length === 0) {\n return null;\n }\n\n return { code: magic.toString(), ids };\n}\n\n/**\n * Insert (or replace) `id: \"<derived>\"` in the config object. Authors do not\n * supply an id; a stray one is overwritten so the route is always the derived,\n * non-enumerable digest — never an author-controlled value.\n */\nfunction injectId(magic: MagicString, configArg: any, id: string): void {\n const existing = (configArg.properties ?? []).find(\n (prop: any) =>\n prop.type === 'Property' &&\n (prop.key?.type === 'Identifier' ? prop.key.name : prop.key?.value) ===\n 'id',\n );\n\n if (existing) {\n magic.overwrite(\n existing.value.start,\n existing.value.end,\n JSON.stringify(id),\n );\n return;\n }\n\n // Insert as the first property, right after the opening brace.\n magic.appendRight(configArg.start + 1, ` id: ${JSON.stringify(id)},`);\n}\n\n/**\n * Reject `method: 'GET'` alongside an `input` schema at build time: GET carries\n * no body, so a GET function can never receive validated input.\n */\nfunction assertMethodInputCompatible(\n configArg: any,\n name: string,\n fileId: string,\n): void {\n let method: string | undefined;\n let hasInput = false;\n for (const prop of configArg.properties ?? []) {\n if (prop.type !== 'Property') continue;\n const key =\n prop.key?.type === 'Identifier' ? prop.key.name : prop.key?.value;\n if (key === 'method' && prop.value?.type === 'Literal') {\n method = prop.value.value;\n } else if (key === 'input') {\n hasInput = true;\n }\n }\n if (method === 'GET' && hasInput) {\n throw new Error(\n `[analog] serverFn \"${name}\" in ${fileId} declares method: 'GET' with an input schema; GET carries no body. Use POST (the default when input is present) or drop the input.`,\n );\n }\n}\n\nfunction isFunctionNode(node: any): boolean {\n return (\n node?.type === 'ArrowFunctionExpression' ||\n node?.type === 'FunctionExpression'\n );\n}\n\nfunction collectServerFnLocalNames(body: any[]): Set<string> {\n const names = new Set<string>();\n for (const node of body) {\n if (\n node.type !== 'ImportDeclaration' ||\n node.source?.value !== SERVER_FN_SOURCE\n ) {\n continue;\n }\n for (const spec of node.specifiers ?? []) {\n if (\n spec.type === 'ImportSpecifier' &&\n spec.imported?.name === 'serverFn'\n ) {\n names.add(spec.local.name);\n }\n }\n }\n if (names.size === 0) {\n names.add('serverFn');\n }\n return names;\n}\n"],"mappings":";;;;AAKA,IAAM,mBAAmB;;;;;;;;;;AAgBzB,SAAgB,kBACd,MACA,QACgC;AAChC,KAAI,CAAC,KAAK,SAAS,WAAW,CAC5B,QAAO;CAGT,MAAM,EAAE,YAAY,UAAU,QAAQ,KAAK;CAC3C,MAAM,OAAQ,QAA4B;CAC1C,MAAM,qBAAqB,0BAA0B,KAAK;CAE1D,MAAM,QAAQ,IAAI,YAAY,KAAK;CACnC,MAAM,MAAsC,EAAE;AAE9C,MAAK,MAAM,QAAQ,MAAM;AACvB,MACE,KAAK,SAAS,4BACd,KAAK,aAAa,SAAS,sBAE3B;AAEF,OAAK,MAAM,cAAc,KAAK,YAAY,cAAc;AACtD,OACE,WAAW,IAAI,SAAS,gBACxB,WAAW,MAAM,SAAS,oBAC1B,WAAW,KAAK,QAAQ,SAAS,gBACjC,CAAC,mBAAmB,IAAI,WAAW,KAAK,OAAO,KAAK,CAEpD;GAGF,MAAM,OAAO,WAAW,GAAG;GAC3B,MAAM,KAAK,iBAAiB,QAAQ,KAAK;AACzC,OAAI,KAAK;IAAE;IAAM;IAAI,CAAC;GAEtB,MAAM,OAAO,WAAW,KAAK,aAAa,EAAE;GAC5C,MAAM,OAAO,KAAK;GAClB,MAAM,SAAS,OAAO,KAAK,UAAU,GAAG;AAExC,OAAI,MAAM,SAAS,oBAAoB;AAErC,gCAA4B,MAAM,MAAM,OAAO;AAC/C,aAAS,OAAO,MAAM,GAAG;cAChB,eAAe,KAAK,CAE7B,OAAM,WAAW,KAAK,OAAO,KAAK,OAAO,MAAM;YACtC,QAAQ,KAAK,UAAU,GAAG;AAEnC,UAAM,WAAW,KAAK,OAAO,KAAK,OAAO,WAAW;AACpD,UAAM,YAAY,KAAK,KAAK,KAAK;SAEjC,OAAM,IAAI,MACR,sBAAsB,KAAK,OAAO,OAAO,gGAC1C;;;AAKP,KAAI,IAAI,WAAW,EACjB,QAAO;AAGT,QAAO;EAAE,MAAM,MAAM,UAAU;EAAE;EAAK;;;;;;;AAQxC,SAAS,SAAS,OAAoB,WAAgB,IAAkB;CACtE,MAAM,YAAY,UAAU,cAAc,EAAE,EAAE,MAC3C,SACC,KAAK,SAAS,eACb,KAAK,KAAK,SAAS,eAAe,KAAK,IAAI,OAAO,KAAK,KAAK,WAC3D,KACL;AAED,KAAI,UAAU;AACZ,QAAM,UACJ,SAAS,MAAM,OACf,SAAS,MAAM,KACf,KAAK,UAAU,GAAG,CACnB;AACD;;AAIF,OAAM,YAAY,UAAU,QAAQ,GAAG,QAAQ,KAAK,UAAU,GAAG,CAAC,GAAG;;;;;;AAOvE,SAAS,4BACP,WACA,MACA,QACM;CACN,IAAI;CACJ,IAAI,WAAW;AACf,MAAK,MAAM,QAAQ,UAAU,cAAc,EAAE,EAAE;AAC7C,MAAI,KAAK,SAAS,WAAY;EAC9B,MAAM,MACJ,KAAK,KAAK,SAAS,eAAe,KAAK,IAAI,OAAO,KAAK,KAAK;AAC9D,MAAI,QAAQ,YAAY,KAAK,OAAO,SAAS,UAC3C,UAAS,KAAK,MAAM;WACX,QAAQ,QACjB,YAAW;;AAGf,KAAI,WAAW,SAAS,SACtB,OAAM,IAAI,MACR,sBAAsB,KAAK,OAAO,OAAO,oIAC1C;;AAIL,SAAS,eAAe,MAAoB;AAC1C,QACE,MAAM,SAAS,6BACf,MAAM,SAAS;;AAInB,SAAS,0BAA0B,MAA0B;CAC3D,MAAM,wBAAQ,IAAI,KAAa;AAC/B,MAAK,MAAM,QAAQ,MAAM;AACvB,MACE,KAAK,SAAS,uBACd,KAAK,QAAQ,UAAU,iBAEvB;AAEF,OAAK,MAAM,QAAQ,KAAK,cAAc,EAAE,CACtC,KACE,KAAK,SAAS,qBACd,KAAK,UAAU,SAAS,WAExB,OAAM,IAAI,KAAK,MAAM,KAAK;;AAIhC,KAAI,MAAM,SAAS,EACjB,OAAM,IAAI,WAAW;AAEvB,QAAO"}