@analogjs/vite-plugin-nitro 3.0.0-alpha.4 → 3.0.0-alpha.41
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 +26 -11
- package/src/index.d.ts +11 -9
- package/src/index.js +7 -2
- package/src/index.js.map +1 -1
- package/src/lib/build-server.d.ts +2 -2
- package/src/lib/build-server.js +45 -149
- package/src/lib/build-server.js.map +1 -1
- package/src/lib/build-sitemap.d.ts +23 -9
- package/src/lib/build-sitemap.js +132 -63
- package/src/lib/build-sitemap.js.map +1 -1
- package/src/lib/build-ssr.d.ts +3 -2
- package/src/lib/build-ssr.js +26 -18
- package/src/lib/build-ssr.js.map +1 -1
- package/src/lib/hooks/post-rendering-hook.d.ts +1 -1
- package/src/lib/hooks/post-rendering-hook.js +10 -6
- package/src/lib/hooks/post-rendering-hook.js.map +1 -1
- package/src/lib/options.d.ts +143 -106
- package/src/lib/plugins/dev-server-plugin.d.ts +3 -3
- package/src/lib/plugins/dev-server-plugin.js +98 -101
- package/src/lib/plugins/dev-server-plugin.js.map +1 -1
- package/src/lib/plugins/page-endpoints.d.ts +5 -5
- package/src/lib/plugins/page-endpoints.js +26 -57
- package/src/lib/plugins/page-endpoints.js.map +1 -1
- package/src/lib/utils/debug.d.ts +5 -0
- package/src/lib/utils/debug.js +15 -0
- package/src/lib/utils/debug.js.map +1 -0
- package/src/lib/utils/get-content-files.d.ts +54 -54
- package/src/lib/utils/get-content-files.js +88 -97
- package/src/lib/utils/get-content-files.js.map +1 -1
- package/src/lib/utils/get-page-handlers.d.ts +58 -58
- package/src/lib/utils/get-page-handlers.js +70 -84
- package/src/lib/utils/get-page-handlers.js.map +1 -1
- package/src/lib/utils/i18n-prerender.d.ts +36 -0
- package/src/lib/utils/i18n-prerender.js +23 -0
- package/src/lib/utils/i18n-prerender.js.map +1 -0
- package/src/lib/utils/load-esm.d.ts +18 -18
- package/src/lib/utils/node-web-bridge.d.ts +1 -1
- package/src/lib/utils/node-web-bridge.js +50 -45
- package/src/lib/utils/node-web-bridge.js.map +1 -1
- package/src/lib/utils/register-dev-middleware.d.ts +12 -12
- package/src/lib/utils/register-dev-middleware.js +41 -47
- package/src/lib/utils/register-dev-middleware.js.map +1 -1
- package/src/lib/utils/register-i18n-watcher.d.ts +15 -0
- package/src/lib/utils/renderers.d.ts +47 -47
- package/src/lib/utils/renderers.js +57 -56
- package/src/lib/utils/renderers.js.map +1 -1
- package/src/lib/utils/rolldown.d.ts +2 -0
- package/src/lib/utils/rolldown.js +12 -0
- package/src/lib/utils/rolldown.js.map +1 -0
- package/src/lib/vite-plugin-nitro.d.ts +3 -3
- package/src/lib/vite-plugin-nitro.js +790 -677
- package/src/lib/vite-plugin-nitro.js.map +1 -1
- package/README.md +0 -125
- package/src/lib/options.js +0 -2
- package/src/lib/options.js.map +0 -1
- package/src/lib/utils/load-esm.js +0 -23
- package/src/lib/utils/load-esm.js.map +0 -1
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
import type { NitroEventHandler } from
|
|
1
|
+
import type { NitroEventHandler } from "nitro/types";
|
|
2
2
|
type GetHandlersArgs = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
workspaceRoot: string;
|
|
4
|
+
sourceRoot: string;
|
|
5
|
+
rootDir: string;
|
|
6
|
+
additionalPagesDirs?: string[];
|
|
7
|
+
hasAPIDir?: boolean;
|
|
8
8
|
};
|
|
9
9
|
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
export declare function getPageHandlers({ workspaceRoot, sourceRoot, rootDir, additionalPagesDirs, hasAPIDir
|
|
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[];
|
|
62
62
|
export {};
|
|
@@ -1,87 +1,73 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
* @param hasAPIDir Whether the project has an API directory (affects route prefixing)
|
|
19
|
+
* @returns Array of NitroEventHandler objects with handler paths and route patterns
|
|
20
|
+
*
|
|
21
|
+
* Example usage:
|
|
22
|
+
* const handlers = getPageHandlers({
|
|
23
|
+
* workspaceRoot: '/workspace',
|
|
24
|
+
* sourceRoot: 'src',
|
|
25
|
+
* rootDir: 'apps/my-app',
|
|
26
|
+
* additionalPagesDirs: ['/libs/shared/pages'],
|
|
27
|
+
* hasAPIDir: true
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* Sample discovered file paths:
|
|
31
|
+
* - /workspace/apps/my-app/src/app/pages/index.server.ts
|
|
32
|
+
* - /workspace/apps/my-app/src/app/pages/users/[id].server.ts
|
|
33
|
+
* - /workspace/apps/my-app/src/app/pages/products/[...slug].server.ts
|
|
34
|
+
* - /workspace/apps/my-app/src/app/pages/(auth)/login.server.ts
|
|
35
|
+
*
|
|
36
|
+
* Route transformation examples:
|
|
37
|
+
* - index.server.ts → /_analog/pages/index
|
|
38
|
+
* - users/[id].server.ts → /_analog/pages/users/:id
|
|
39
|
+
* - products/[...slug].server.ts → /_analog/pages/products/**:slug
|
|
40
|
+
* - (auth)/login.server.ts → /_analog/pages/-auth-/login
|
|
41
|
+
*
|
|
42
|
+
* tinyglobby vs fast-glob comparison:
|
|
43
|
+
* - Both support the same glob patterns for file discovery
|
|
44
|
+
* - Both are efficient for finding server-side page files
|
|
45
|
+
* - tinyglobby is now used instead of fast-glob
|
|
46
|
+
* - tinyglobby provides similar functionality with smaller bundle size
|
|
47
|
+
* - tinyglobby's globSync returns absolute paths when absolute: true is set
|
|
48
|
+
*
|
|
49
|
+
* Route transformation rules:
|
|
50
|
+
* 1. Removes .server.ts extension
|
|
51
|
+
* 2. Converts [param] to :param for dynamic routes
|
|
52
|
+
* 3. Converts [...param] to **:param for catch-all routes
|
|
53
|
+
* 4. Converts (group) to -group- for route groups
|
|
54
|
+
* 5. Converts dots to forward slashes
|
|
55
|
+
* 6. Prefixes with /_analog/pages and optionally /api
|
|
56
|
+
*/
|
|
57
|
+
function getPageHandlers({ workspaceRoot, sourceRoot, rootDir, additionalPagesDirs, hasAPIDir }) {
|
|
58
|
+
return globSync([`${normalizePath(resolve(workspaceRoot, rootDir))}/${sourceRoot}/app/pages/**/*.server.ts`, ...(additionalPagesDirs || []).map((dir) => `${workspaceRoot}${dir}/**/*.server.ts`)], {
|
|
59
|
+
dot: true,
|
|
60
|
+
absolute: true
|
|
61
|
+
}).map((endpointFile) => {
|
|
62
|
+
const route = 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
|
+
return {
|
|
64
|
+
handler: endpointFile,
|
|
65
|
+
route: `${hasAPIDir ? "/api" : ""}/_analog${route}`,
|
|
66
|
+
lazy: true
|
|
67
|
+
};
|
|
68
|
+
});
|
|
86
69
|
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { getPageHandlers };
|
|
72
|
+
|
|
87
73
|
//# sourceMappingURL=get-page-handlers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-page-handlers.js","
|
|
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 hasAPIDir?: boolean;\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 * @param hasAPIDir Whether the project has an API directory (affects route prefixing)\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 * hasAPIDir: true\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 → /_analog/pages/index\n * - users/[id].server.ts → /_analog/pages/users/:id\n * - products/[...slug].server.ts → /_analog/pages/products/**:slug\n * - (auth)/login.server.ts → /_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 /_analog/pages and optionally /api\n */\nexport function getPageHandlers({\n workspaceRoot,\n sourceRoot,\n rootDir,\n additionalPagesDirs,\n hasAPIDir,\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: `${hasAPIDir ? '/api' : ''}/_analog${route}`,\n lazy: true,\n };\n });\n\n return handlers;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkEA,SAAgB,gBAAgB,EAC9B,eACA,YACA,SACA,qBACA,aACuC;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;EAIxE,MAAM,QAFa,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;AAGtB,SAAO;GACL,SAAS;GACT,OAAO,GAAG,YAAY,SAAS,GAAG,UAAU;GAC5C,MAAM;GACP;GACD"}
|
|
@@ -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"}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { URL } from
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
import { URL } from "node:url";
|
|
9
9
|
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
* This uses a dynamic import to load a module which may be ESM.
|
|
11
|
+
* CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
|
|
12
|
+
* will currently, unconditionally downlevel dynamic import into a require call.
|
|
13
|
+
* require calls cannot load ESM code and will result in a runtime error. To workaround
|
|
14
|
+
* this, a Function constructor is used to prevent TypeScript from changing the dynamic import.
|
|
15
|
+
* Once TypeScript provides support for keeping the dynamic import this workaround can
|
|
16
|
+
* be dropped.
|
|
17
|
+
*
|
|
18
|
+
* @param modulePath The path of the module to load.
|
|
19
|
+
* @returns A Promise that resolves to the dynamically imported module.
|
|
20
|
+
*/
|
|
21
21
|
export declare function loadEsmModule<T>(modulePath: string | URL): Promise<T>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { IncomingMessage, ServerResponse } from
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
export declare function toWebRequest(req: IncomingMessage): Request;
|
|
3
3
|
export declare function writeWebResponseToNode(res: ServerResponse, response: Response): Promise<void>;
|
|
@@ -1,50 +1,55 @@
|
|
|
1
|
-
import { Readable } from
|
|
2
|
-
import { pipeline } from
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
import { pipeline } from "node:stream/promises";
|
|
3
|
+
//#region packages/vite-plugin-nitro/src/lib/utils/node-web-bridge.ts
|
|
3
4
|
function toWebHeaders(headers) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return acc;
|
|
9
|
-
}, new Headers());
|
|
5
|
+
return Object.entries(headers).reduce((acc, [key, value]) => {
|
|
6
|
+
if (value && !key.startsWith(":")) acc.set(key, Array.isArray(value) ? value.join(", ") : value);
|
|
7
|
+
return acc;
|
|
8
|
+
}, new Headers());
|
|
10
9
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// @ts-expect-error duplex is required for streaming request bodies in Node.js
|
|
23
|
-
duplex: body ? 'half' : undefined,
|
|
24
|
-
});
|
|
10
|
+
function toWebRequest(req) {
|
|
11
|
+
const protocol = "http";
|
|
12
|
+
const host = req.headers.host || "localhost";
|
|
13
|
+
const url = new URL(req.url || "/", `${protocol}://${host}`);
|
|
14
|
+
const body = req.method && !["GET", "HEAD"].includes(req.method) ? Readable.toWeb(req) : void 0;
|
|
15
|
+
return new Request(url, {
|
|
16
|
+
method: req.method,
|
|
17
|
+
headers: toWebHeaders(req.headers),
|
|
18
|
+
body,
|
|
19
|
+
duplex: body ? "half" : void 0
|
|
20
|
+
});
|
|
25
21
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
response.headers.forEach((value, key) => {
|
|
37
|
-
if (key !== 'set-cookie') {
|
|
38
|
-
res.setHeader(key, value);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
if (!response.body) {
|
|
42
|
-
res.end();
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
// The Web ReadableStream and Node.js stream/web ReadableStream types
|
|
46
|
-
// are structurally identical at runtime but TypeScript treats them as
|
|
47
|
-
// distinct nominal types. The double-cast bridges this gap safely.
|
|
48
|
-
await pipeline(Readable.fromWeb(response.body), res);
|
|
22
|
+
function isClientDisconnectError(error, res) {
|
|
23
|
+
if (!(error instanceof Error)) return false;
|
|
24
|
+
const hasDisconnectCode = "code" in error && typeof error.code === "string" && [
|
|
25
|
+
"ERR_STREAM_PREMATURE_CLOSE",
|
|
26
|
+
"ERR_INVALID_STATE",
|
|
27
|
+
"ECONNRESET",
|
|
28
|
+
"EPIPE"
|
|
29
|
+
].includes(error.code);
|
|
30
|
+
const hasDisconnectMessage = /closed or destroyed stream/i.test(error.message);
|
|
31
|
+
return (res.destroyed || res.writableEnded) && (hasDisconnectCode || hasDisconnectMessage);
|
|
49
32
|
}
|
|
33
|
+
async function writeWebResponseToNode(res, response) {
|
|
34
|
+
res.statusCode = response.status;
|
|
35
|
+
res.statusMessage = response.statusText;
|
|
36
|
+
const setCookies = "getSetCookie" in response.headers && typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : [];
|
|
37
|
+
if (setCookies.length > 0) res.setHeader("set-cookie", setCookies);
|
|
38
|
+
response.headers.forEach((value, key) => {
|
|
39
|
+
if (key !== "set-cookie") res.setHeader(key, value);
|
|
40
|
+
});
|
|
41
|
+
if (!response.body) {
|
|
42
|
+
res.end();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
await pipeline(Readable.fromWeb(response.body), res);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (isClientDisconnectError(error, res)) return;
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { toWebRequest, writeWebResponseToNode };
|
|
54
|
+
|
|
50
55
|
//# sourceMappingURL=node-web-bridge.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-web-bridge.js","
|
|
1
|
+
{"version":3,"file":"node-web-bridge.js","names":[],"sources":["../../../../src/lib/utils/node-web-bridge.ts"],"sourcesContent":["import type {\n IncomingHttpHeaders,\n IncomingMessage,\n ServerResponse,\n} from 'node:http';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\n\nfunction toWebHeaders(headers: IncomingHttpHeaders) {\n return Object.entries(headers).reduce((acc, [key, value]) => {\n if (value && !key.startsWith(':')) {\n acc.set(key, Array.isArray(value) ? value.join(', ') : value);\n }\n\n return acc;\n }, new Headers());\n}\n\nexport function toWebRequest(req: IncomingMessage): Request {\n const protocol = 'http';\n const host = req.headers.host || 'localhost';\n const url = new URL(req.url || '/', `${protocol}://${host}`);\n const body =\n req.method && !['GET', 'HEAD'].includes(req.method)\n ? (Readable.toWeb(req) as ReadableStream<Uint8Array>)\n : undefined;\n\n return new Request(url, {\n method: req.method,\n headers: toWebHeaders(req.headers),\n body,\n // @ts-expect-error duplex is required for streaming request bodies in Node.js\n duplex: body ? 'half' : undefined,\n });\n}\n\nfunction isClientDisconnectError(error: unknown, res: ServerResponse): boolean {\n if (!(error instanceof Error)) {\n return false;\n }\n\n const hasDisconnectCode =\n 'code' in error &&\n typeof error.code === 'string' &&\n [\n 'ERR_STREAM_PREMATURE_CLOSE',\n 'ERR_INVALID_STATE',\n 'ECONNRESET',\n 'EPIPE',\n ].includes(error.code);\n\n const hasDisconnectMessage = /closed or destroyed stream/i.test(\n error.message,\n );\n\n return (\n (res.destroyed || res.writableEnded) &&\n (hasDisconnectCode || hasDisconnectMessage)\n );\n}\n\nexport async function writeWebResponseToNode(\n res: ServerResponse,\n response: Response,\n): Promise<void> {\n res.statusCode = response.status;\n res.statusMessage = response.statusText;\n\n const setCookies =\n 'getSetCookie' in response.headers &&\n typeof response.headers.getSetCookie === 'function'\n ? response.headers.getSetCookie()\n : [];\n\n if (setCookies.length > 0) {\n res.setHeader('set-cookie', setCookies);\n }\n\n response.headers.forEach((value, key) => {\n if (key !== 'set-cookie') {\n res.setHeader(key, value);\n }\n });\n\n if (!response.body) {\n res.end();\n return;\n }\n\n // The Web ReadableStream and Node.js stream/web ReadableStream types\n // are structurally identical at runtime but TypeScript treats them as\n // distinct nominal types. The double-cast bridges this gap safely.\n try {\n await pipeline(\n Readable.fromWeb(\n response.body as unknown as import('node:stream/web').ReadableStream,\n ),\n res,\n );\n } catch (error) {\n // Long-lived dev responses such as SSE can be interrupted by a browser\n // refresh or HMR-triggered reconnect. Those closed-stream cases are\n // expected and should not surface as noisy server errors.\n if (isClientDisconnectError(error, res)) {\n return;\n }\n\n throw error;\n }\n}\n"],"mappings":";;;AAQA,SAAS,aAAa,SAA8B;AAClD,QAAO,OAAO,QAAQ,QAAQ,CAAC,QAAQ,KAAK,CAAC,KAAK,WAAW;AAC3D,MAAI,SAAS,CAAC,IAAI,WAAW,IAAI,CAC/B,KAAI,IAAI,KAAK,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK,KAAK,GAAG,MAAM;AAG/D,SAAO;IACN,IAAI,SAAS,CAAC;;AAGnB,SAAgB,aAAa,KAA+B;CAC1D,MAAM,WAAW;CACjB,MAAM,OAAO,IAAI,QAAQ,QAAQ;CACjC,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,GAAG,SAAS,KAAK,OAAO;CAC5D,MAAM,OACJ,IAAI,UAAU,CAAC,CAAC,OAAO,OAAO,CAAC,SAAS,IAAI,OAAO,GAC9C,SAAS,MAAM,IAAI,GACpB,KAAA;AAEN,QAAO,IAAI,QAAQ,KAAK;EACtB,QAAQ,IAAI;EACZ,SAAS,aAAa,IAAI,QAAQ;EAClC;EAEA,QAAQ,OAAO,SAAS,KAAA;EACzB,CAAC;;AAGJ,SAAS,wBAAwB,OAAgB,KAA8B;AAC7E,KAAI,EAAE,iBAAiB,OACrB,QAAO;CAGT,MAAM,oBACJ,UAAU,SACV,OAAO,MAAM,SAAS,YACtB;EACE;EACA;EACA;EACA;EACD,CAAC,SAAS,MAAM,KAAK;CAExB,MAAM,uBAAuB,8BAA8B,KACzD,MAAM,QACP;AAED,SACG,IAAI,aAAa,IAAI,mBACrB,qBAAqB;;AAI1B,eAAsB,uBACpB,KACA,UACe;AACf,KAAI,aAAa,SAAS;AAC1B,KAAI,gBAAgB,SAAS;CAE7B,MAAM,aACJ,kBAAkB,SAAS,WAC3B,OAAO,SAAS,QAAQ,iBAAiB,aACrC,SAAS,QAAQ,cAAc,GAC/B,EAAE;AAER,KAAI,WAAW,SAAS,EACtB,KAAI,UAAU,cAAc,WAAW;AAGzC,UAAS,QAAQ,SAAS,OAAO,QAAQ;AACvC,MAAI,QAAQ,aACV,KAAI,UAAU,KAAK,MAAM;GAE3B;AAEF,KAAI,CAAC,SAAS,MAAM;AAClB,MAAI,KAAK;AACT;;AAMF,KAAI;AACF,QAAM,SACJ,SAAS,QACP,SAAS,KACV,EACD,IACD;UACM,OAAO;AAId,MAAI,wBAAwB,OAAO,IAAI,CACrC;AAGF,QAAM"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { ViteDevServer } from
|
|
1
|
+
import { ViteDevServer } from "vite";
|
|
2
2
|
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
* Registers development server middleware by discovering and loading middleware files.
|
|
4
|
+
*
|
|
5
|
+
* Each discovered h3 middleware module is loaded through Vite's SSR loader,
|
|
6
|
+
* wrapped in a temporary H3 app, then bridged back into Vite's Connect stack.
|
|
7
|
+
* If the middleware does not write a response, control falls through to the
|
|
8
|
+
* next Vite middleware.
|
|
9
|
+
*
|
|
10
|
+
* @param root The project root directory path
|
|
11
|
+
* @param sourceRoot The source directory path (e.g., 'src')
|
|
12
|
+
* @param viteServer The Vite development server instance
|
|
13
|
+
*/
|
|
14
14
|
export declare function registerDevServerMiddleware(root: string, sourceRoot: string, viteServer: ViteDevServer): Promise<void>;
|