@vaadin/hilla-file-router 24.4.0-alpha8 → 24.4.0-alpha9
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 +6 -3
- package/runtime/createMenuItems.d.ts +2 -32
- package/runtime/createMenuItems.d.ts.map +1 -1
- package/runtime/createMenuItems.js.map +2 -2
- package/shared/internal.d.ts +21 -0
- package/types.d.ts +10 -21
- package/vite-plugin/collectRoutesFromFS.d.ts +17 -4
- package/vite-plugin/collectRoutesFromFS.d.ts.map +1 -1
- package/vite-plugin/collectRoutesFromFS.js +29 -4
- package/vite-plugin/collectRoutesFromFS.js.map +2 -2
- package/vite-plugin/createViewConfigJson.d.ts.map +1 -1
- package/vite-plugin/createViewConfigJson.js.map +2 -2
- package/vite-plugin/generateRuntimeFiles.js +1 -1
- package/vite-plugin/generateRuntimeFiles.js.map +2 -2
- package/vite-plugin.d.ts.map +1 -1
- package/vite-plugin.js +16 -0
- package/vite-plugin.js.map +2 -2
- package/types.d.js +0 -1
- package/types.d.js.map +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/hilla-file-router",
|
|
3
|
-
"version": "24.4.0-
|
|
3
|
+
"version": "24.4.0-alpha9",
|
|
4
4
|
"description": "Hilla file-based router",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"build": "concurrently npm:build:*",
|
|
24
24
|
"build:esbuild": "tsx ../../../scripts/build.ts",
|
|
25
25
|
"build:dts": "tsc --isolatedModules -p tsconfig.build.json",
|
|
26
|
-
"build:copy": "cd src && copyfiles **/*.d.ts ..",
|
|
26
|
+
"build:copy": "cd src && copyfiles *.d.ts **/*.d.ts ..",
|
|
27
27
|
"lint": "eslint src test",
|
|
28
28
|
"lint:fix": "eslint src test --fix",
|
|
29
29
|
"test": "mocha test/**/*.spec.ts --config ../../../.mocharc.cjs",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"@types/deep-equal-in-any-order": "^1.0.3",
|
|
69
69
|
"@types/mocha": "^10.0.6",
|
|
70
70
|
"@types/sinon": "^10.0.17",
|
|
71
|
+
"@types/sinon-chai": "^3.2.12",
|
|
71
72
|
"chai-as-promised": "^7.1.1",
|
|
72
73
|
"chai-fs": "^2.0.0",
|
|
73
74
|
"chai-like": "^1.1.1",
|
|
@@ -75,11 +76,13 @@
|
|
|
75
76
|
"mocha": "^10.2.0",
|
|
76
77
|
"rimraf": "^5.0.5",
|
|
77
78
|
"sinon": "^16.0.0",
|
|
79
|
+
"sinon-chai": "^3.7.0",
|
|
78
80
|
"type-fest": "^4.9.0"
|
|
79
81
|
},
|
|
80
82
|
"dependencies": {
|
|
81
|
-
"@vaadin/hilla-generator-utils": "24.4.0-
|
|
83
|
+
"@vaadin/hilla-generator-utils": "24.4.0-alpha9",
|
|
82
84
|
"react": "^18.2.0",
|
|
85
|
+
"rollup": "^4.12.0",
|
|
83
86
|
"typescript": "5.3.2"
|
|
84
87
|
}
|
|
85
88
|
}
|
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { ViewConfig } from '../types.js';
|
|
3
|
-
export type VaadinServer = Readonly<{
|
|
4
|
-
views: Record<string, ViewConfig>;
|
|
5
|
-
}>;
|
|
6
|
-
export type VaadinObject = Readonly<{
|
|
7
|
-
server?: VaadinServer;
|
|
8
|
-
}>;
|
|
9
|
-
declare global {
|
|
10
|
-
interface Window {
|
|
11
|
-
Vaadin?: VaadinObject;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
export type MenuItem = Readonly<{
|
|
15
|
-
to: string;
|
|
16
|
-
icon?: string;
|
|
17
|
-
title?: string;
|
|
18
|
-
}>;
|
|
1
|
+
import type { MenuItem } from '../types.js';
|
|
19
2
|
/**
|
|
20
3
|
* Creates menu items from the views provided by the server. The views are sorted according to the
|
|
21
4
|
* {@link ViewConfig.menu.order} and filtered out if they are explicitly excluded via {@link ViewConfig.menu.exclude}.
|
|
@@ -25,20 +8,7 @@ export type MenuItem = Readonly<{
|
|
|
25
8
|
*/
|
|
26
9
|
export declare function createMenuItems(vaadinObject?: Readonly<{
|
|
27
10
|
server?: Readonly<{
|
|
28
|
-
views: Record<string,
|
|
29
|
-
title?: string | undefined;
|
|
30
|
-
params?: Readonly<Record<string, RouteParamType>> | undefined;
|
|
31
|
-
rolesAllowed?: string[] | undefined;
|
|
32
|
-
route?: string | undefined;
|
|
33
|
-
lazy?: boolean | undefined;
|
|
34
|
-
register?: boolean | undefined;
|
|
35
|
-
menu?: Readonly<{
|
|
36
|
-
title?: string | undefined;
|
|
37
|
-
order?: number | undefined;
|
|
38
|
-
exclude?: boolean | undefined;
|
|
39
|
-
icon?: string | undefined;
|
|
40
|
-
}> | undefined;
|
|
41
|
-
}>>;
|
|
11
|
+
views: Readonly<Record<string, import("../shared/internal.js").ServerViewConfig>>;
|
|
42
12
|
}> | undefined;
|
|
43
13
|
}> | undefined): readonly MenuItem[];
|
|
44
14
|
//# sourceMappingURL=createMenuItems.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createMenuItems.d.ts","sourceRoot":"","sources":["../src/runtime/createMenuItems.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"createMenuItems.d.ts","sourceRoot":"","sources":["../src/runtime/createMenuItems.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,aAAa,CAAC;AAExD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY;;;;cAAkC,GAAG,SAAS,QAAQ,EAAE,CAwBnG"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/runtime/createMenuItems.ts"],
|
|
4
|
-
"sourcesContent": ["import {
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { VaadinWindow } from '../shared/internal.js';\nimport { RouteParamType } from '../shared/routeParamType.js';\nimport type { MenuItem, ViewConfig } from '../types.js';\n\n/**\n * Creates menu items from the views provided by the server. The views are sorted according to the\n * {@link ViewConfig.menu.order} and filtered out if they are explicitly excluded via {@link ViewConfig.menu.exclude}.\n *\n * @param vaadinObject - The Vaadin object containing the server views.\n * @returns A list of menu items.\n */\nexport function createMenuItems(vaadinObject = (window as VaadinWindow).Vaadin): readonly MenuItem[] {\n return vaadinObject?.server?.views\n ? Object.entries(vaadinObject.server.views)\n // Filter out the views that are explicitly excluded from the menu.\n .filter(\n ([_key, value]) =>\n !value.menu?.exclude &&\n !(value.params && Object.values(value.params).some((p) => p === RouteParamType.Required)),\n )\n // Sort views according to the order specified in the view configuration.\n .sort(([_a, a], [_b, b]) => (a.menu?.order ?? 0) - (b.menu?.order ?? 0))\n // Map the views to menu items.\n .map(([path, config]) => {\n const _path = config.params\n ? Object.keys(config.params).reduce((acc, key) => acc.replaceAll(key, ''), path)\n : path;\n\n return {\n to: _path,\n icon: config.menu?.icon,\n title: config.menu?.title ?? config.title,\n };\n })\n : [];\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,sBAAsB;AAUxB,SAAS,gBAAgB,eAAgB,OAAwB,QAA6B;AACnG,SAAO,cAAc,QAAQ,QACzB,OAAO,QAAQ,aAAa,OAAO,KAAK,EAErC;AAAA,IACC,CAAC,CAAC,MAAM,KAAK,MACX,CAAC,MAAM,MAAM,WACb,EAAE,MAAM,UAAU,OAAO,OAAO,MAAM,MAAM,EAAE,KAAK,CAAC,MAAM,MAAM,eAAe,QAAQ;AAAA,EAC3F,EAEC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,SAAS,MAAM,EAAE,MAAM,SAAS,EAAE,EAEtE,IAAI,CAAC,CAAC,MAAM,MAAM,MAAM;AACvB,UAAM,QAAQ,OAAO,SACjB,OAAO,KAAK,OAAO,MAAM,EAAE,OAAO,CAAC,KAAK,QAAQ,IAAI,WAAW,KAAK,EAAE,GAAG,IAAI,IAC7E;AAEJ,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,MAAM,OAAO,MAAM;AAAA,MACnB,OAAO,OAAO,MAAM,SAAS,OAAO;AAAA,IACtC;AAAA,EACF,CAAC,IACH,CAAC;AACP;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ViewConfig } from '../types.js';
|
|
2
|
+
import type { RouteParamType } from './routeParamType.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Internal type used for server communication and menu building. It extends the
|
|
6
|
+
* view configuration with the route parameters.
|
|
7
|
+
*/
|
|
8
|
+
export type ServerViewConfig = Readonly<{ params?: Readonly<Record<string, RouteParamType>> }> & ViewConfig;
|
|
9
|
+
|
|
10
|
+
export type VaadinServer = Readonly<{
|
|
11
|
+
views: Readonly<Record<string, ServerViewConfig>>;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
export type VaadinObject = Readonly<{
|
|
15
|
+
server?: VaadinServer;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
export type VaadinWindow = Readonly<{
|
|
19
|
+
Vaadin?: VaadinObject;
|
|
20
|
+
}> &
|
|
21
|
+
Window;
|
package/types.d.ts
CHANGED
|
@@ -8,15 +8,10 @@ export type ViewConfig = Readonly<{
|
|
|
8
8
|
*/
|
|
9
9
|
title?: string;
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
* A map of route parameters and their types.
|
|
13
|
-
*/
|
|
14
|
-
params?: Readonly<Record<string, RouteParamType>>;
|
|
15
|
-
|
|
16
11
|
/**
|
|
17
12
|
* Same as in the explicit React Router configuration.
|
|
18
13
|
*/
|
|
19
|
-
rolesAllowed?: string[];
|
|
14
|
+
rolesAllowed?: readonly string[];
|
|
20
15
|
|
|
21
16
|
/**
|
|
22
17
|
* Allows overriding the route path configuration. Uses the same syntax as
|
|
@@ -25,21 +20,6 @@ export type ViewConfig = Readonly<{
|
|
|
25
20
|
*/
|
|
26
21
|
route?: string;
|
|
27
22
|
|
|
28
|
-
/**
|
|
29
|
-
* Controls whether the view implementation will be lazy loaded the first time
|
|
30
|
-
* it's used or always included in the bundle. If set to undefined (which is
|
|
31
|
-
* the default), views mapped to / and /login will be eager and any other view
|
|
32
|
-
* will be lazy (this is in sync with defaults in Flow)
|
|
33
|
-
*/
|
|
34
|
-
lazy?: boolean;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* If set to false, then the route will not be registered with React Router,
|
|
38
|
-
* but it will still be included in the main menu and used to configure
|
|
39
|
-
* Spring Security
|
|
40
|
-
*/
|
|
41
|
-
register?: boolean;
|
|
42
|
-
|
|
43
23
|
menu?: Readonly<{
|
|
44
24
|
/**
|
|
45
25
|
* Title to use in the menu. Falls back the title property of the view
|
|
@@ -81,3 +61,12 @@ export type AgnosticRoute<C = unknown> = Readonly<{
|
|
|
81
61
|
module?: RouteModule<C>;
|
|
82
62
|
children?: ReadonlyArray<AgnosticRoute<T>>;
|
|
83
63
|
}>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A menu item used in for building the navigation menu.
|
|
67
|
+
*/
|
|
68
|
+
export type MenuItem = Readonly<{
|
|
69
|
+
to: string;
|
|
70
|
+
icon?: string;
|
|
71
|
+
title?: string;
|
|
72
|
+
}>;
|
|
@@ -1,12 +1,27 @@
|
|
|
1
|
+
import type { Logger } from 'vite';
|
|
1
2
|
export type RouteMeta = Readonly<{
|
|
2
3
|
path: string;
|
|
3
4
|
file?: URL;
|
|
4
5
|
layout?: URL;
|
|
5
6
|
children: RouteMeta[];
|
|
6
7
|
}>;
|
|
8
|
+
/**
|
|
9
|
+
* Routes collector options.
|
|
10
|
+
*/
|
|
7
11
|
export type CollectRoutesOptions = Readonly<{
|
|
12
|
+
/**
|
|
13
|
+
* The list of extensions for files that will be collected as routes.
|
|
14
|
+
*/
|
|
8
15
|
extensions: readonly string[];
|
|
16
|
+
/**
|
|
17
|
+
* The parent directory of the current directory. This is a
|
|
18
|
+
* nested parameter used inside the function only.
|
|
19
|
+
*/
|
|
9
20
|
parent?: URL;
|
|
21
|
+
/**
|
|
22
|
+
* The Vite logger instance.
|
|
23
|
+
*/
|
|
24
|
+
logger: Logger;
|
|
10
25
|
}>;
|
|
11
26
|
/**
|
|
12
27
|
* Collect route metadata from the file system and build a route tree.
|
|
@@ -18,11 +33,9 @@ export type CollectRoutesOptions = Readonly<{
|
|
|
18
33
|
* It accepts files that start with `_` as private files. They will be ignored.
|
|
19
34
|
*
|
|
20
35
|
* @param dir - The directory to collect routes from.
|
|
21
|
-
* @param
|
|
22
|
-
* @param parent - The parent directory of the current directory. This is a nested parameter used inside the function
|
|
23
|
-
* only.
|
|
36
|
+
* @param options - The options object.
|
|
24
37
|
*
|
|
25
38
|
* @returns The route metadata tree.
|
|
26
39
|
*/
|
|
27
|
-
export default function collectRoutesFromFS(dir: URL, { extensions, parent }: CollectRoutesOptions): Promise<RouteMeta>;
|
|
40
|
+
export default function collectRoutesFromFS(dir: URL, { extensions, logger, parent }: CollectRoutesOptions): Promise<RouteMeta>;
|
|
28
41
|
//# sourceMappingURL=collectRoutesFromFS.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collectRoutesFromFS.d.ts","sourceRoot":"","sources":["../src/vite-plugin/collectRoutesFromFS.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"collectRoutesFromFS.d.ts","sourceRoot":"","sources":["../src/vite-plugin/collectRoutesFromFS.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,QAAQ,EAAE,SAAS,EAAE,CAAC;CACvB,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C;;OAEG;IACH,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,GAAG,CAAC;IACb;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAiBH;;;;;;;;;;;;;GAaG;AACH,wBAA8B,mBAAmB,CAC/C,GAAG,EAAE,GAAG,EACR,EAAE,UAAU,EAAE,MAAM,EAAE,MAAY,EAAE,EAAE,oBAAoB,GACzD,OAAO,CAAC,SAAS,CAAC,CAuDpB"}
|
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
import { opendir } from "node:fs/promises";
|
|
1
|
+
import { opendir, readFile } from "node:fs/promises";
|
|
2
2
|
import { basename, extname, relative } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { cleanUp } from "./utils.js";
|
|
5
|
+
async function checkFile(url, logger) {
|
|
6
|
+
if (url) {
|
|
7
|
+
const contents = await readFile(url, "utf-8");
|
|
8
|
+
if (contents === "") {
|
|
9
|
+
return void 0;
|
|
10
|
+
} else if (!contents.includes("export default")) {
|
|
11
|
+
logger.error(`The file "${String(url)}" should contain a default export of a component`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return url;
|
|
15
|
+
}
|
|
5
16
|
const collator = new Intl.Collator("en-US");
|
|
6
|
-
async function collectRoutesFromFS(dir, { extensions, parent = dir }) {
|
|
17
|
+
async function collectRoutesFromFS(dir, { extensions, logger, parent = dir }) {
|
|
7
18
|
const path = relative(fileURLToPath(parent), fileURLToPath(dir));
|
|
8
|
-
|
|
19
|
+
let children = [];
|
|
9
20
|
let layout;
|
|
10
21
|
for await (const d of await opendir(dir)) {
|
|
11
22
|
if (d.isDirectory()) {
|
|
12
|
-
children.push(await collectRoutesFromFS(new URL(`${d.name}/`, dir), { extensions, parent: dir }));
|
|
23
|
+
children.push(await collectRoutesFromFS(new URL(`${d.name}/`, dir), { extensions, logger, parent: dir }));
|
|
13
24
|
} else if (d.isFile() && extensions.includes(extname(d.name))) {
|
|
14
25
|
const file = new URL(d.name, dir);
|
|
15
26
|
const name = basename(d.name, extname(d.name));
|
|
@@ -34,6 +45,20 @@ async function collectRoutesFromFS(dir, { extensions, parent = dir }) {
|
|
|
34
45
|
}
|
|
35
46
|
}
|
|
36
47
|
}
|
|
48
|
+
[children, layout] = await Promise.all([
|
|
49
|
+
Promise.all(
|
|
50
|
+
children.map(async (child) => {
|
|
51
|
+
let { file: f, layout: l } = child;
|
|
52
|
+
[f, l] = await Promise.all([checkFile(f, logger), checkFile(l, logger)]);
|
|
53
|
+
return {
|
|
54
|
+
...child,
|
|
55
|
+
file: f,
|
|
56
|
+
layout: l
|
|
57
|
+
};
|
|
58
|
+
})
|
|
59
|
+
),
|
|
60
|
+
checkFile(layout, logger)
|
|
61
|
+
]);
|
|
37
62
|
return {
|
|
38
63
|
path,
|
|
39
64
|
layout,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/vite-plugin/collectRoutesFromFS.ts"],
|
|
4
|
-
"sourcesContent": ["import { opendir } from 'node:fs/promises';\nimport { basename, extname, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Logger } from 'vite';\nimport { cleanUp } from './utils.js';\n\nexport type RouteMeta = Readonly<{\n path: string;\n file?: URL;\n layout?: URL;\n children: RouteMeta[];\n}>;\n\nexport type CollectRoutesOptions = Readonly<{\n extensions: readonly string[];\n parent?: URL;\n}>;\n\nconst collator = new Intl.Collator('en-US');\n\n/**\n * Collect route metadata from the file system and build a route tree.\n *\n * It accepts files that start with `$` as special files.\n * - `$layout` contains a component that wraps the child components.\n * - `$index` contains a component that will be used as the index page of the directory.\n *\n * It accepts files that start with `_` as private files. They will be ignored.\n *\n * @param dir - The directory to collect routes from.\n * @param
|
|
5
|
-
"mappings": "AAAA,SAAS,
|
|
4
|
+
"sourcesContent": ["import { opendir, readFile } from 'node:fs/promises';\nimport { basename, extname, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Logger } from 'vite';\nimport { cleanUp } from './utils.js';\n\nexport type RouteMeta = Readonly<{\n path: string;\n file?: URL;\n layout?: URL;\n children: RouteMeta[];\n}>;\n\n/**\n * Routes collector options.\n */\nexport type CollectRoutesOptions = Readonly<{\n /**\n * The list of extensions for files that will be collected as routes.\n */\n extensions: readonly string[];\n /**\n * The parent directory of the current directory. This is a\n * nested parameter used inside the function only.\n */\n parent?: URL;\n /**\n * The Vite logger instance.\n */\n logger: Logger;\n}>;\n\nasync function checkFile(url: URL | undefined, logger: Logger): Promise<URL | undefined> {\n if (url) {\n const contents = await readFile(url, 'utf-8');\n if (contents === '') {\n return undefined;\n } else if (!contents.includes('export default')) {\n logger.error(`The file \"${String(url)}\" should contain a default export of a component`);\n }\n }\n\n return url;\n}\n\nconst collator = new Intl.Collator('en-US');\n\n/**\n * Collect route metadata from the file system and build a route tree.\n *\n * It accepts files that start with `$` as special files.\n * - `$layout` contains a component that wraps the child components.\n * - `$index` contains a component that will be used as the index page of the directory.\n *\n * It accepts files that start with `_` as private files. They will be ignored.\n *\n * @param dir - The directory to collect routes from.\n * @param options - The options object.\n *\n * @returns The route metadata tree.\n */\nexport default async function collectRoutesFromFS(\n dir: URL,\n { extensions, logger, parent = dir }: CollectRoutesOptions,\n): Promise<RouteMeta> {\n const path = relative(fileURLToPath(parent), fileURLToPath(dir));\n let children: RouteMeta[] = [];\n let layout: URL | undefined;\n\n for await (const d of await opendir(dir)) {\n if (d.isDirectory()) {\n children.push(await collectRoutesFromFS(new URL(`${d.name}/`, dir), { extensions, logger, parent: dir }));\n } else if (d.isFile() && extensions.includes(extname(d.name))) {\n const file = new URL(d.name, dir);\n const name = basename(d.name, extname(d.name));\n\n if (name.startsWith('$')) {\n if (name === '$layout') {\n layout = file;\n } else if (name === '$index') {\n children.push({\n path: '',\n file,\n children: [],\n });\n } else {\n throw new Error('Symbol \"$\" is reserved for special files; only \"$layout\" and \"$index\" are allowed');\n }\n } else if (!name.startsWith('_')) {\n children.push({\n path: name,\n file,\n children: [],\n });\n }\n }\n }\n\n [children, layout] = await Promise.all([\n Promise.all(\n children.map(async (child) => {\n let { file: f, layout: l } = child;\n [f, l] = await Promise.all([checkFile(f, logger), checkFile(l, logger)]);\n\n return {\n ...child,\n file: f,\n layout: l,\n };\n }),\n ),\n checkFile(layout, logger),\n ]);\n\n return {\n path,\n layout,\n children: children.sort(({ path: a }, { path: b }) => collator.compare(cleanUp(a), cleanUp(b))),\n };\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS,gBAAgB;AAClC,SAAS,UAAU,SAAS,gBAAgB;AAC5C,SAAS,qBAAqB;AAE9B,SAAS,eAAe;AA4BxB,eAAe,UAAU,KAAsB,QAA0C;AACvF,MAAI,KAAK;AACP,UAAM,WAAW,MAAM,SAAS,KAAK,OAAO;AAC5C,QAAI,aAAa,IAAI;AACnB,aAAO;AAAA,IACT,WAAW,CAAC,SAAS,SAAS,gBAAgB,GAAG;AAC/C,aAAO,MAAM,aAAa,OAAO,GAAG,CAAC,kDAAkD;AAAA,IACzF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,MAAM,WAAW,IAAI,KAAK,SAAS,OAAO;AAgB1C,eAAO,oBACL,KACA,EAAE,YAAY,QAAQ,SAAS,IAAI,GACf;AACpB,QAAM,OAAO,SAAS,cAAc,MAAM,GAAG,cAAc,GAAG,CAAC;AAC/D,MAAI,WAAwB,CAAC;AAC7B,MAAI;AAEJ,mBAAiB,KAAK,MAAM,QAAQ,GAAG,GAAG;AACxC,QAAI,EAAE,YAAY,GAAG;AACnB,eAAS,KAAK,MAAM,oBAAoB,IAAI,IAAI,GAAG,EAAE,IAAI,KAAK,GAAG,GAAG,EAAE,YAAY,QAAQ,QAAQ,IAAI,CAAC,CAAC;AAAA,IAC1G,WAAW,EAAE,OAAO,KAAK,WAAW,SAAS,QAAQ,EAAE,IAAI,CAAC,GAAG;AAC7D,YAAM,OAAO,IAAI,IAAI,EAAE,MAAM,GAAG;AAChC,YAAM,OAAO,SAAS,EAAE,MAAM,QAAQ,EAAE,IAAI,CAAC;AAE7C,UAAI,KAAK,WAAW,GAAG,GAAG;AACxB,YAAI,SAAS,WAAW;AACtB,mBAAS;AAAA,QACX,WAAW,SAAS,UAAU;AAC5B,mBAAS,KAAK;AAAA,YACZ,MAAM;AAAA,YACN;AAAA,YACA,UAAU,CAAC;AAAA,UACb,CAAC;AAAA,QACH,OAAO;AACL,gBAAM,IAAI,MAAM,mFAAmF;AAAA,QACrG;AAAA,MACF,WAAW,CAAC,KAAK,WAAW,GAAG,GAAG;AAChC,iBAAS,KAAK;AAAA,UACZ,MAAM;AAAA,UACN;AAAA,UACA,UAAU,CAAC;AAAA,QACb,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,GAAC,UAAU,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,IACrC,QAAQ;AAAA,MACN,SAAS,IAAI,OAAO,UAAU;AAC5B,YAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,IAAI;AAC7B,SAAC,GAAG,CAAC,IAAI,MAAM,QAAQ,IAAI,CAAC,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC,CAAC;AAEvE,eAAO;AAAA,UACL,GAAG;AAAA,UACH,MAAM;AAAA,UACN,QAAQ;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,UAAU,QAAQ,MAAM;AAAA,EAC1B,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,UAAU,SAAS,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,SAAS,QAAQ,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;AAAA,EAChG;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createViewConfigJson.d.ts","sourceRoot":"","sources":["../src/vite-plugin/createViewConfigJson.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"createViewConfigJson.d.ts","sourceRoot":"","sources":["../src/vite-plugin/createViewConfigJson.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAgB1D;;;;;GAKG;AACH,wBAA8B,oBAAoB,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CA2DpF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/vite-plugin/createViewConfigJson.ts"],
|
|
4
|
-
"sourcesContent": ["import { readFile } from 'node:fs/promises';\nimport { Script } from 'node:vm';\nimport ts, { type Node } from 'typescript';\nimport { convertComponentNameToTitle } from '../shared/convertComponentNameToTitle.js';\nimport traverse from '../shared/traverse.js';\nimport type { ViewConfig } from '../types.js';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\nimport { convertFSRouteSegmentToURLPatternFormat, extractParameterFromRouteSegment } from './utils.js';\n\n/**\n * Walks the TypeScript AST using the deep-first search algorithm.\n *\n * @param node - The node to walk.\n */\nfunction* walkAST(node: Node): Generator<Node> {\n yield node;\n\n for (const child of node.getChildren()) {\n yield* walkAST(child);\n }\n}\n\n/**\n * Creates a map of all leaf routes to their configuration. This file is used by the server to provide server-side\n * routes along with managing the client-side routes.\n *\n * @param views - The route metadata tree.\n */\nexport default async function createViewConfigJson(views: RouteMeta): Promise<string> {\n const res = await Promise.all(\n Array.from(traverse(views), async (branch) => {\n const configs = await Promise.all(\n branch.map(async ({ path, file, layout }): Promise<[string,
|
|
5
|
-
"mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,OAAO,YAAuB;AAC9B,SAAS,mCAAmC;
|
|
4
|
+
"sourcesContent": ["import { readFile } from 'node:fs/promises';\nimport { Script } from 'node:vm';\nimport ts, { type Node } from 'typescript';\nimport { convertComponentNameToTitle } from '../shared/convertComponentNameToTitle.js';\nimport type { ServerViewConfig } from '../shared/internal.js';\nimport traverse from '../shared/traverse.js';\nimport type { ViewConfig } from '../types.js';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\nimport { convertFSRouteSegmentToURLPatternFormat, extractParameterFromRouteSegment } from './utils.js';\n\n/**\n * Walks the TypeScript AST using the deep-first search algorithm.\n *\n * @param node - The node to walk.\n */\nfunction* walkAST(node: Node): Generator<Node> {\n yield node;\n\n for (const child of node.getChildren()) {\n yield* walkAST(child);\n }\n}\n\n/**\n * Creates a map of all leaf routes to their configuration. This file is used by the server to provide server-side\n * routes along with managing the client-side routes.\n *\n * @param views - The route metadata tree.\n */\nexport default async function createViewConfigJson(views: RouteMeta): Promise<string> {\n const res = await Promise.all(\n Array.from(traverse(views), async (branch) => {\n const configs = await Promise.all(\n branch.map(async ({ path, file, layout }): Promise<[string, ServerViewConfig]> => {\n if (!file && !layout) {\n return [\n convertFSRouteSegmentToURLPatternFormat(path),\n { params: extractParameterFromRouteSegment(path) },\n ] as const;\n }\n\n const sourceFile = ts.createSourceFile(\n 'f.ts',\n await readFile(file ?? layout!, 'utf8'),\n ts.ScriptTarget.ESNext,\n true,\n );\n let config: ViewConfig | undefined;\n let waitingForIdentifier = false;\n let componentName: string | undefined;\n\n for (const node of walkAST(sourceFile)) {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === 'config') {\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const code = node.initializer.getText(sourceFile);\n const script = new Script(`(${code})`);\n config = script.runInThisContext() as ViewConfig;\n }\n } else if (node.getText(sourceFile).startsWith('export default')) {\n waitingForIdentifier = true;\n } else if (waitingForIdentifier && ts.isIdentifier(node)) {\n componentName = node.text;\n break;\n }\n }\n const _path = config?.route ?? path;\n const pattern = convertFSRouteSegmentToURLPatternFormat(_path);\n\n return [\n pattern,\n {\n ...config,\n params: extractParameterFromRouteSegment(_path),\n title: config?.title ?? convertComponentNameToTitle(componentName),\n },\n ] as const;\n }),\n );\n\n const key = configs.map(([path]) => path).join('/');\n const params = configs.reduce((acc, [, { params: p }]) => Object.assign(acc, p), {});\n const [, value] = configs[configs.length - 1];\n\n return [key, { ...value, params: Object.keys(params).length > 0 ? params : undefined }] as const;\n }),\n );\n\n return JSON.stringify(Object.fromEntries(res));\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,OAAO,YAAuB;AAC9B,SAAS,mCAAmC;AAE5C,OAAO,cAAc;AAGrB,SAAS,yCAAyC,wCAAwC;AAO1F,UAAU,QAAQ,MAA6B;AAC7C,QAAM;AAEN,aAAW,SAAS,KAAK,YAAY,GAAG;AACtC,WAAO,QAAQ,KAAK;AAAA,EACtB;AACF;AAQA,eAAO,qBAA4C,OAAmC;AACpF,QAAM,MAAM,MAAM,QAAQ;AAAA,IACxB,MAAM,KAAK,SAAS,KAAK,GAAG,OAAO,WAAW;AAC5C,YAAM,UAAU,MAAM,QAAQ;AAAA,QAC5B,OAAO,IAAI,OAAO,EAAE,MAAM,MAAM,OAAO,MAA2C;AAChF,cAAI,CAAC,QAAQ,CAAC,QAAQ;AACpB,mBAAO;AAAA,cACL,wCAAwC,IAAI;AAAA,cAC5C,EAAE,QAAQ,iCAAiC,IAAI,EAAE;AAAA,YACnD;AAAA,UACF;AAEA,gBAAM,aAAa,GAAG;AAAA,YACpB;AAAA,YACA,MAAM,SAAS,QAAQ,QAAS,MAAM;AAAA,YACtC,GAAG,aAAa;AAAA,YAChB;AAAA,UACF;AACA,cAAI;AACJ,cAAI,uBAAuB;AAC3B,cAAI;AAEJ,qBAAW,QAAQ,QAAQ,UAAU,GAAG;AACtC,gBAAI,GAAG,sBAAsB,IAAI,KAAK,GAAG,aAAa,KAAK,IAAI,KAAK,KAAK,KAAK,SAAS,UAAU;AAC/F,kBAAI,KAAK,eAAe,GAAG,0BAA0B,KAAK,WAAW,GAAG;AACtE,sBAAM,OAAO,KAAK,YAAY,QAAQ,UAAU;AAChD,sBAAM,SAAS,IAAI,OAAO,IAAI,IAAI,GAAG;AACrC,yBAAS,OAAO,iBAAiB;AAAA,cACnC;AAAA,YACF,WAAW,KAAK,QAAQ,UAAU,EAAE,WAAW,gBAAgB,GAAG;AAChE,qCAAuB;AAAA,YACzB,WAAW,wBAAwB,GAAG,aAAa,IAAI,GAAG;AACxD,8BAAgB,KAAK;AACrB;AAAA,YACF;AAAA,UACF;AACA,gBAAM,QAAQ,QAAQ,SAAS;AAC/B,gBAAM,UAAU,wCAAwC,KAAK;AAE7D,iBAAO;AAAA,YACL;AAAA,YACA;AAAA,cACE,GAAG;AAAA,cACH,QAAQ,iCAAiC,KAAK;AAAA,cAC9C,OAAO,QAAQ,SAAS,4BAA4B,aAAa;AAAA,YACnE;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH;AAEA,YAAM,MAAM,QAAQ,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI,EAAE,KAAK,GAAG;AAClD,YAAM,SAAS,QAAQ,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,MAAM,OAAO,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;AACnF,YAAM,CAAC,EAAE,KAAK,IAAI,QAAQ,QAAQ,SAAS,CAAC;AAE5C,aAAO,CAAC,KAAK,EAAE,GAAG,OAAO,QAAQ,OAAO,KAAK,MAAM,EAAE,SAAS,IAAI,SAAS,OAAU,CAAC;AAAA,IACxF,CAAC;AAAA,EACH;AAEA,SAAO,KAAK,UAAU,OAAO,YAAY,GAAG,CAAC;AAC/C;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -17,7 +17,7 @@ async function generateRuntimeFile(url, data) {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
async function generateRuntimeFiles(viewsDir, urls, extensions, logger) {
|
|
20
|
-
const routeMeta = await collectRoutesFromFS(viewsDir, { extensions });
|
|
20
|
+
const routeMeta = await collectRoutesFromFS(viewsDir, { extensions, logger });
|
|
21
21
|
logger.info("Collected file-based routes");
|
|
22
22
|
const runtimeRoutesCode = createRoutesFromMeta(routeMeta, urls);
|
|
23
23
|
const viewConfigJson = await createViewConfigJson(routeMeta);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/vite-plugin/generateRuntimeFiles.ts"],
|
|
4
|
-
"sourcesContent": ["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport type { Logger } from 'vite';\nimport collectRoutesFromFS from './collectRoutesFromFS.js';\nimport createRoutesFromMeta from './createRoutesFromMeta.js';\nimport createViewConfigJson from './createViewConfigJson.js';\n\n/**\n * The URLs of the files to generate.\n */\nexport type RuntimeFileUrls = Readonly<{\n /**\n * The URL of the JSON file with the leaf routes and their metadata. This file\n * will be processed by the server to provide the final route configuration.\n */\n json: URL;\n /**\n * The URL of the module with the routes tree in a framework-agnostic format.\n */\n code: URL;\n}>;\n\n/**\n * Generates a file conditionally. If the file already exists and its content is the same as the\n * given data, the file will not be overwritten. It is useful to avoid unnecessary server\n * reboot during development.\n *\n * @param url - The URL of the file to generate.\n * @param data - The data to write to the file.\n */\nasync function generateRuntimeFile(url: URL, data: string): Promise<void> {\n await mkdir(new URL('./', url), { recursive: true });\n let contents: string | undefined;\n try {\n contents = await readFile(url, 'utf-8');\n } catch (e: unknown) {\n if (!(e != null && typeof e === 'object' && 'code' in e && e.code === 'ENOENT')) {\n throw e;\n }\n }\n if (contents !== data) {\n await writeFile(url, data, 'utf-8');\n }\n}\n\n/**\n * Collects all file-based routes from the given directory, and based on them generates two files\n * described by {@link RuntimeFileUrls} type.\n * @param viewsDir - The directory that contains file-based routes (views).\n * @param urls - The URLs of the files to generate.\n * @param extensions - The list of extensions that will be collected as routes.\n * @param logger - The Vite logger instance.\n */\nexport async function generateRuntimeFiles(\n viewsDir: URL,\n urls: RuntimeFileUrls,\n extensions: readonly string[],\n logger: Logger,\n): Promise<void> {\n const routeMeta = await collectRoutesFromFS(viewsDir, { extensions });\n logger.info('Collected file-based routes');\n const runtimeRoutesCode = createRoutesFromMeta(routeMeta, urls);\n const viewConfigJson = await createViewConfigJson(routeMeta);\n\n await Promise.all([\n generateRuntimeFile(urls.json, viewConfigJson).then(() =>\n logger.info(`Frontend route list is generated: ${String(urls.json)}`),\n ),\n generateRuntimeFile(urls.code, runtimeRoutesCode).then(() =>\n logger.info(`Views module is generated: ${String(urls.code)}`),\n ),\n ]);\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,OAAO,UAAU,iBAAiB;AAE3C,OAAO,yBAAyB;AAChC,OAAO,0BAA0B;AACjC,OAAO,0BAA0B;AAyBjC,eAAe,oBAAoB,KAAU,MAA6B;AACxE,QAAM,MAAM,IAAI,IAAI,MAAM,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,SAAS,KAAK,OAAO;AAAA,EACxC,SAAS,GAAY;AACnB,QAAI,EAAE,KAAK,QAAQ,OAAO,MAAM,YAAY,UAAU,KAAK,EAAE,SAAS,WAAW;AAC/E,YAAM;AAAA,IACR;AAAA,EACF;AACA,MAAI,aAAa,MAAM;AACrB,UAAM,UAAU,KAAK,MAAM,OAAO;AAAA,EACpC;AACF;AAUA,eAAsB,qBACpB,UACA,MACA,YACA,QACe;AACf,QAAM,YAAY,MAAM,oBAAoB,UAAU,EAAE,
|
|
4
|
+
"sourcesContent": ["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport type { Logger } from 'vite';\nimport collectRoutesFromFS from './collectRoutesFromFS.js';\nimport createRoutesFromMeta from './createRoutesFromMeta.js';\nimport createViewConfigJson from './createViewConfigJson.js';\n\n/**\n * The URLs of the files to generate.\n */\nexport type RuntimeFileUrls = Readonly<{\n /**\n * The URL of the JSON file with the leaf routes and their metadata. This file\n * will be processed by the server to provide the final route configuration.\n */\n json: URL;\n /**\n * The URL of the module with the routes tree in a framework-agnostic format.\n */\n code: URL;\n}>;\n\n/**\n * Generates a file conditionally. If the file already exists and its content is the same as the\n * given data, the file will not be overwritten. It is useful to avoid unnecessary server\n * reboot during development.\n *\n * @param url - The URL of the file to generate.\n * @param data - The data to write to the file.\n */\nasync function generateRuntimeFile(url: URL, data: string): Promise<void> {\n await mkdir(new URL('./', url), { recursive: true });\n let contents: string | undefined;\n try {\n contents = await readFile(url, 'utf-8');\n } catch (e: unknown) {\n if (!(e != null && typeof e === 'object' && 'code' in e && e.code === 'ENOENT')) {\n throw e;\n }\n }\n if (contents !== data) {\n await writeFile(url, data, 'utf-8');\n }\n}\n\n/**\n * Collects all file-based routes from the given directory, and based on them generates two files\n * described by {@link RuntimeFileUrls} type.\n * @param viewsDir - The directory that contains file-based routes (views).\n * @param urls - The URLs of the files to generate.\n * @param extensions - The list of extensions that will be collected as routes.\n * @param logger - The Vite logger instance.\n */\nexport async function generateRuntimeFiles(\n viewsDir: URL,\n urls: RuntimeFileUrls,\n extensions: readonly string[],\n logger: Logger,\n): Promise<void> {\n const routeMeta = await collectRoutesFromFS(viewsDir, { extensions, logger });\n logger.info('Collected file-based routes');\n const runtimeRoutesCode = createRoutesFromMeta(routeMeta, urls);\n const viewConfigJson = await createViewConfigJson(routeMeta);\n\n await Promise.all([\n generateRuntimeFile(urls.json, viewConfigJson).then(() =>\n logger.info(`Frontend route list is generated: ${String(urls.json)}`),\n ),\n generateRuntimeFile(urls.code, runtimeRoutesCode).then(() =>\n logger.info(`Views module is generated: ${String(urls.code)}`),\n ),\n ]);\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,OAAO,UAAU,iBAAiB;AAE3C,OAAO,yBAAyB;AAChC,OAAO,0BAA0B;AACjC,OAAO,0BAA0B;AAyBjC,eAAe,oBAAoB,KAAU,MAA6B;AACxE,QAAM,MAAM,IAAI,IAAI,MAAM,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,SAAS,KAAK,OAAO;AAAA,EACxC,SAAS,GAAY;AACnB,QAAI,EAAE,KAAK,QAAQ,OAAO,MAAM,YAAY,UAAU,KAAK,EAAE,SAAS,WAAW;AAC/E,YAAM;AAAA,IACR;AAAA,EACF;AACA,MAAI,aAAa,MAAM;AACrB,UAAM,UAAU,KAAK,MAAM,OAAO;AAAA,EACpC;AACF;AAUA,eAAsB,qBACpB,UACA,MACA,YACA,QACe;AACf,QAAM,YAAY,MAAM,oBAAoB,UAAU,EAAE,YAAY,OAAO,CAAC;AAC5E,SAAO,KAAK,6BAA6B;AACzC,QAAM,oBAAoB,qBAAqB,WAAW,IAAI;AAC9D,QAAM,iBAAiB,MAAM,qBAAqB,SAAS;AAE3D,QAAM,QAAQ,IAAI;AAAA,IAChB,oBAAoB,KAAK,MAAM,cAAc,EAAE;AAAA,MAAK,MAClD,OAAO,KAAK,qCAAqC,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,IACtE;AAAA,IACA,oBAAoB,KAAK,MAAM,iBAAiB,EAAE;AAAA,MAAK,MACrD,OAAO,KAAK,8BAA8B,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,IAC/D;AAAA,EACF,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/vite-plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["src/vite-plugin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["src/vite-plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAG3C;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC;IACxB;;;;OAIG;IACH,YAAY,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,0BAA0B,CAAC,EACjD,QAA4B,EAC5B,YAAoC,EACpC,UAA2C,GAC5C,GAAE,aAAkB,GAAG,MAAM,CA8E7B"}
|
package/vite-plugin.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
1
2
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
2
3
|
import { generateRuntimeFiles } from "./vite-plugin/generateRuntimeFiles.js";
|
|
3
4
|
function vitePluginFileSystemRouter({
|
|
@@ -5,6 +6,7 @@ function vitePluginFileSystemRouter({
|
|
|
5
6
|
generatedDir = "frontend/generated/",
|
|
6
7
|
extensions = [".tsx", ".jsx", ".ts", ".js"]
|
|
7
8
|
} = {}) {
|
|
9
|
+
const hmrInjectionPattern = /(?<=import\.meta\.hot\.accept[\s\S]+)if\s\(!nextExports\)\s+return;/u;
|
|
8
10
|
let _viewsDir;
|
|
9
11
|
let _outDir;
|
|
10
12
|
let _logger;
|
|
@@ -45,6 +47,20 @@ function vitePluginFileSystemRouter({
|
|
|
45
47
|
server.watcher.on("add", changeListener);
|
|
46
48
|
server.watcher.on("change", changeListener);
|
|
47
49
|
server.watcher.on("unlink", changeListener);
|
|
50
|
+
},
|
|
51
|
+
transform(code, id) {
|
|
52
|
+
if (id.startsWith(fileURLToPath(_viewsDir)) && !basename(id).startsWith("_")) {
|
|
53
|
+
return {
|
|
54
|
+
code: code.replace(
|
|
55
|
+
hmrInjectionPattern,
|
|
56
|
+
`if (!nextExports) return;
|
|
57
|
+
if (Object.keys(nextExports).length === 2 && 'default' in nextExports && 'config' in nextExports) {
|
|
58
|
+
nextExports = { ...nextExports, config: currentExports.config };
|
|
59
|
+
}`
|
|
60
|
+
)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return void 0;
|
|
48
64
|
}
|
|
49
65
|
};
|
|
50
66
|
}
|
package/vite-plugin.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["src/vite-plugin.ts"],
|
|
4
|
-
"sourcesContent": ["import { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { Logger, Plugin } from 'vite';\nimport { generateRuntimeFiles, type RuntimeFileUrls } from './vite-plugin/generateRuntimeFiles.js';\n\n/**\n * The options for the Vite file-based router plugin.\n */\nexport type PluginOptions = Readonly<{\n /**\n * The base directory for the router. The folders and files in this directory\n * will be used as route paths.\n *\n * @defaultValue `frontend/views`\n */\n viewsDir?: URL | string;\n /**\n * The directory where the generated view file will be stored.\n *\n * @defaultValue `frontend/generated`\n */\n generatedDir?: URL | string;\n /**\n * The list of extensions that will be collected as routes of the file-based\n * router.\n *\n * @defaultValue `['.tsx', '.jsx', '.ts', '.js']`\n */\n extensions?: readonly string[];\n}>;\n\n/**\n * A Vite plugin that generates a router from the files in the specific directory.\n *\n * @param options - The plugin options.\n * @returns A Vite plugin.\n */\nexport default function vitePluginFileSystemRouter({\n viewsDir = 'frontend/views/',\n generatedDir = 'frontend/generated/',\n extensions = ['.tsx', '.jsx', '.ts', '.js'],\n}: PluginOptions = {}): Plugin {\n let _viewsDir: URL;\n let _outDir: URL;\n let _logger: Logger;\n let runtimeUrls: RuntimeFileUrls;\n\n return {\n name: 'vite-plugin-file-router',\n configResolved({ logger, root, build: { outDir } }) {\n const _root = pathToFileURL(root);\n const _generatedDir = new URL(generatedDir, _root);\n\n _viewsDir = new URL(viewsDir, _root);\n _outDir = pathToFileURL(outDir);\n _logger = logger;\n\n _logger.info(`The directory of route files: ${String(_viewsDir)}`);\n _logger.info(`The directory of generated files: ${String(_generatedDir)}`);\n _logger.info(`The output directory: ${String(_outDir)}`);\n\n runtimeUrls = {\n json: new URL('views.json', _outDir),\n code: new URL('views.ts', _generatedDir),\n };\n },\n async buildStart() {\n try {\n await generateRuntimeFiles(_viewsDir, runtimeUrls, extensions, _logger);\n } catch (e: unknown) {\n _logger.error(String(e));\n }\n },\n configureServer(server) {\n const dir = fileURLToPath(_viewsDir);\n\n const changeListener = (file: string): void => {\n if (!file.startsWith(dir)) {\n return;\n }\n\n generateRuntimeFiles(_viewsDir, runtimeUrls, extensions, _logger).catch((e: unknown) =>\n _logger.error(String(e)),\n );\n };\n\n server.watcher.on('add', changeListener);\n server.watcher.on('change', changeListener);\n server.watcher.on('unlink', changeListener);\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,eAAe,qBAAqB;
|
|
4
|
+
"sourcesContent": ["import { basename } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { TransformResult } from 'rollup';\nimport type { Logger, Plugin } from 'vite';\nimport { generateRuntimeFiles, type RuntimeFileUrls } from './vite-plugin/generateRuntimeFiles.js';\n\n/**\n * The options for the Vite file-based router plugin.\n */\nexport type PluginOptions = Readonly<{\n /**\n * The base directory for the router. The folders and files in this directory\n * will be used as route paths.\n *\n * @defaultValue `frontend/views`\n */\n viewsDir?: URL | string;\n /**\n * The directory where the generated view file will be stored.\n *\n * @defaultValue `frontend/generated`\n */\n generatedDir?: URL | string;\n /**\n * The list of extensions that will be collected as routes of the file-based\n * router.\n *\n * @defaultValue `['.tsx', '.jsx', '.ts', '.js']`\n */\n extensions?: readonly string[];\n}>;\n\n/**\n * A Vite plugin that generates a router from the files in the specific directory.\n *\n * @param options - The plugin options.\n * @returns A Vite plugin.\n */\nexport default function vitePluginFileSystemRouter({\n viewsDir = 'frontend/views/',\n generatedDir = 'frontend/generated/',\n extensions = ['.tsx', '.jsx', '.ts', '.js'],\n}: PluginOptions = {}): Plugin {\n const hmrInjectionPattern = /(?<=import\\.meta\\.hot\\.accept[\\s\\S]+)if\\s\\(!nextExports\\)\\s+return;/u;\n\n let _viewsDir: URL;\n let _outDir: URL;\n let _logger: Logger;\n let runtimeUrls: RuntimeFileUrls;\n\n return {\n name: 'vite-plugin-file-router',\n configResolved({ logger, root, build: { outDir } }) {\n const _root = pathToFileURL(root);\n const _generatedDir = new URL(generatedDir, _root);\n\n _viewsDir = new URL(viewsDir, _root);\n _outDir = pathToFileURL(outDir);\n _logger = logger;\n\n _logger.info(`The directory of route files: ${String(_viewsDir)}`);\n _logger.info(`The directory of generated files: ${String(_generatedDir)}`);\n _logger.info(`The output directory: ${String(_outDir)}`);\n\n runtimeUrls = {\n json: new URL('views.json', _outDir),\n code: new URL('views.ts', _generatedDir),\n };\n },\n async buildStart() {\n try {\n await generateRuntimeFiles(_viewsDir, runtimeUrls, extensions, _logger);\n } catch (e: unknown) {\n _logger.error(String(e));\n }\n },\n configureServer(server) {\n const dir = fileURLToPath(_viewsDir);\n\n const changeListener = (file: string): void => {\n if (!file.startsWith(dir)) {\n return;\n }\n\n generateRuntimeFiles(_viewsDir, runtimeUrls, extensions, _logger).catch((e: unknown) =>\n _logger.error(String(e)),\n );\n };\n\n server.watcher.on('add', changeListener);\n server.watcher.on('change', changeListener);\n server.watcher.on('unlink', changeListener);\n },\n transform(code, id): Promise<TransformResult> | TransformResult {\n if (id.startsWith(fileURLToPath(_viewsDir)) && !basename(id).startsWith('_')) {\n // To enable HMR for route files with exported configurations, we need\n // to address a limitation in `react-refresh`. This library requires\n // strict equality (`===`) for non-component exports. However, the\n // dynamic nature of HMR makes maintaining this equality between object\n // literals challenging.\n //\n // To work around this, we implement a strategy that preserves the\n // reference to the original configuration object (`currentExports.config`),\n // replacing any newly created configuration objects (`nextExports.config`)\n // with it. This ensures that the HMR mechanism perceives the\n // configuration as unchanged.\n return {\n code: code.replace(\n hmrInjectionPattern,\n `if (!nextExports) return;\n if (Object.keys(nextExports).length === 2 && 'default' in nextExports && 'config' in nextExports) {\n nextExports = { ...nextExports, config: currentExports.config };\n }`,\n ),\n };\n }\n\n return undefined;\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,eAAe,qBAAqB;AAG7C,SAAS,4BAAkD;AAkC5C,SAAR,2BAA4C;AAAA,EACjD,WAAW;AAAA,EACX,eAAe;AAAA,EACf,aAAa,CAAC,QAAQ,QAAQ,OAAO,KAAK;AAC5C,IAAmB,CAAC,GAAW;AAC7B,QAAM,sBAAsB;AAE5B,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,eAAe,EAAE,QAAQ,MAAM,OAAO,EAAE,OAAO,EAAE,GAAG;AAClD,YAAM,QAAQ,cAAc,IAAI;AAChC,YAAM,gBAAgB,IAAI,IAAI,cAAc,KAAK;AAEjD,kBAAY,IAAI,IAAI,UAAU,KAAK;AACnC,gBAAU,cAAc,MAAM;AAC9B,gBAAU;AAEV,cAAQ,KAAK,iCAAiC,OAAO,SAAS,CAAC,EAAE;AACjE,cAAQ,KAAK,qCAAqC,OAAO,aAAa,CAAC,EAAE;AACzE,cAAQ,KAAK,yBAAyB,OAAO,OAAO,CAAC,EAAE;AAEvD,oBAAc;AAAA,QACZ,MAAM,IAAI,IAAI,cAAc,OAAO;AAAA,QACnC,MAAM,IAAI,IAAI,YAAY,aAAa;AAAA,MACzC;AAAA,IACF;AAAA,IACA,MAAM,aAAa;AACjB,UAAI;AACF,cAAM,qBAAqB,WAAW,aAAa,YAAY,OAAO;AAAA,MACxE,SAAS,GAAY;AACnB,gBAAQ,MAAM,OAAO,CAAC,CAAC;AAAA,MACzB;AAAA,IACF;AAAA,IACA,gBAAgB,QAAQ;AACtB,YAAM,MAAM,cAAc,SAAS;AAEnC,YAAM,iBAAiB,CAAC,SAAuB;AAC7C,YAAI,CAAC,KAAK,WAAW,GAAG,GAAG;AACzB;AAAA,QACF;AAEA,6BAAqB,WAAW,aAAa,YAAY,OAAO,EAAE;AAAA,UAAM,CAAC,MACvE,QAAQ,MAAM,OAAO,CAAC,CAAC;AAAA,QACzB;AAAA,MACF;AAEA,aAAO,QAAQ,GAAG,OAAO,cAAc;AACvC,aAAO,QAAQ,GAAG,UAAU,cAAc;AAC1C,aAAO,QAAQ,GAAG,UAAU,cAAc;AAAA,IAC5C;AAAA,IACA,UAAU,MAAM,IAAgD;AAC9D,UAAI,GAAG,WAAW,cAAc,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,WAAW,GAAG,GAAG;AAY5E,eAAO;AAAA,UACL,MAAM,KAAK;AAAA,YACT;AAAA,YACA;AAAA;AAAA;AAAA;AAAA,UAIF;AAAA,QACF;AAAA,MACF;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/types.d.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=types.d.js.map
|