@vaadin/hilla-file-router 24.4.0-alpha4
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/.lintstagedrc.js +6 -0
- package/package.json +76 -0
- package/runtime/utils.d.ts +58 -0
- package/runtime/utils.d.ts.map +1 -0
- package/runtime/utils.js +33 -0
- package/runtime/utils.js.map +7 -0
- package/runtime.d.ts +18 -0
- package/runtime.d.ts.map +1 -0
- package/runtime.js +28 -0
- package/runtime.js.map +7 -0
- package/vite-plugin/collectRoutesFromFS.d.ts +12 -0
- package/vite-plugin/collectRoutesFromFS.d.ts.map +1 -0
- package/vite-plugin/collectRoutesFromFS.js +46 -0
- package/vite-plugin/collectRoutesFromFS.js.map +7 -0
- package/vite-plugin/createRoutesFromMeta.d.ts +3 -0
- package/vite-plugin/createRoutesFromMeta.d.ts.map +1 -0
- package/vite-plugin/createRoutesFromMeta.js +77 -0
- package/vite-plugin/createRoutesFromMeta.js.map +7 -0
- package/vite-plugin/createViewConfigJson.d.ts +3 -0
- package/vite-plugin/createViewConfigJson.d.ts.map +1 -0
- package/vite-plugin/createViewConfigJson.js +56 -0
- package/vite-plugin/createViewConfigJson.js.map +7 -0
- package/vite-plugin/utils.d.ts +20 -0
- package/vite-plugin/utils.d.ts.map +1 -0
- package/vite-plugin/utils.js +14 -0
- package/vite-plugin/utils.js.map +7 -0
- package/vite-plugin-file-router.d.ts +38 -0
- package/vite-plugin-file-router.d.ts.map +1 -0
- package/vite-plugin-file-router.js +57 -0
- package/vite-plugin-file-router.js.map +7 -0
package/.lintstagedrc.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vaadin/hilla-file-router",
|
|
3
|
+
"version": "24.4.0-alpha4",
|
|
4
|
+
"description": "Hilla file-based router",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"module": "index.js",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/vaadin/hilla.git",
|
|
11
|
+
"directory": "packages/ts/hilla-file-router"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"Hilla",
|
|
15
|
+
"Vite",
|
|
16
|
+
"Plugin",
|
|
17
|
+
"File",
|
|
18
|
+
"Router",
|
|
19
|
+
"Routing"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"clean:build": "git clean -fx . -e .vite -e node_modules",
|
|
23
|
+
"build": "concurrently npm:build:*",
|
|
24
|
+
"build:esbuild": "tsx ../../../scripts/build.ts",
|
|
25
|
+
"build:dts": "tsc --isolatedModules -p tsconfig.build.json",
|
|
26
|
+
"build:copy": "cd src && copyfiles **/*.d.ts ..",
|
|
27
|
+
"lint": "eslint src test",
|
|
28
|
+
"lint:fix": "eslint src test --fix",
|
|
29
|
+
"test": "mocha test/**/*.spec.ts --config ../../../.mocharc.cjs",
|
|
30
|
+
"test:coverage": "c8 -c ../../../.c8rc.json npm test",
|
|
31
|
+
"typecheck": "tsc --noEmit"
|
|
32
|
+
},
|
|
33
|
+
"exports": {
|
|
34
|
+
"./runtime.js": {
|
|
35
|
+
"default": "./runtime.js"
|
|
36
|
+
},
|
|
37
|
+
"./vite-plugin-file-router.js": {
|
|
38
|
+
"default": "./vite-plugin-file-router.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"author": "Vaadin Ltd",
|
|
42
|
+
"license": "Apache-2.0",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/vaadin/hilla/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://vaadin.com",
|
|
47
|
+
"files": [
|
|
48
|
+
"*.{d.ts.map,d.ts,js.map,js}",
|
|
49
|
+
"runtime",
|
|
50
|
+
"vite-plugin"
|
|
51
|
+
],
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"react-router": "^6.21.1"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@esm-bundle/chai": "^4.3.4-fix.0",
|
|
60
|
+
"@types/chai-like": "^1.1.3",
|
|
61
|
+
"@types/deep-equal-in-any-order": "^1.0.3",
|
|
62
|
+
"@types/mocha": "^10.0.6",
|
|
63
|
+
"@types/sinon": "^17.0.3",
|
|
64
|
+
"chai-like": "^1.1.1",
|
|
65
|
+
"deep-equal-in-any-order": "^2.0.6",
|
|
66
|
+
"mocha": "^10.2.0",
|
|
67
|
+
"rimraf": "^5.0.5",
|
|
68
|
+
"sinon": "^17.0.1",
|
|
69
|
+
"type-fest": "^4.9.0",
|
|
70
|
+
"typescript": "^5.3.3"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@vaadin/hilla-generator-utils": "24.4.0-alpha4",
|
|
74
|
+
"react": "^18.2.0"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type ViewConfig = Readonly<{
|
|
2
|
+
/**
|
|
3
|
+
* View title used in the main layout header, as <title> and as the default
|
|
4
|
+
* for the menu entry. If not defined, then the view function name is converted
|
|
5
|
+
* from CamelCase after removing any "View" postfix.
|
|
6
|
+
*/
|
|
7
|
+
title?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Same as in the explicit React Router configuration.
|
|
10
|
+
*/
|
|
11
|
+
rolesAllowed?: string[];
|
|
12
|
+
/**
|
|
13
|
+
* Allows overriding the route path configuration. Uses the same syntax as
|
|
14
|
+
* the path property with React Router. This can be used to define a route
|
|
15
|
+
* that conflicts with the file name conventions, e.g. /foo/index
|
|
16
|
+
*/
|
|
17
|
+
route?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Controls whether the view implementation will be lazy loaded the first time
|
|
20
|
+
* it's used or always included in the bundle. If set to undefined (which is
|
|
21
|
+
* the default), views mapped to / and /login will be eager and any other view
|
|
22
|
+
* will be lazy (this is in sync with defaults in Flow)
|
|
23
|
+
*/
|
|
24
|
+
lazy?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* If set to false, then the route will not be registered with React Router,
|
|
27
|
+
* but it will still be included in the main menu and used to configure
|
|
28
|
+
* Spring Security
|
|
29
|
+
*/
|
|
30
|
+
register?: boolean;
|
|
31
|
+
menu?: Readonly<{
|
|
32
|
+
/**
|
|
33
|
+
* Title to use in the menu. Falls back the title property of the view
|
|
34
|
+
* itself if not defined.
|
|
35
|
+
*/
|
|
36
|
+
title?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Used to determine the order in the menu. Ties are resolved based on the
|
|
39
|
+
* used title. Entries without explicitly defined ordering are put below
|
|
40
|
+
* entries with an order.
|
|
41
|
+
*/
|
|
42
|
+
order?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Set to true to explicitly exclude a view from the automatically
|
|
45
|
+
* populated menu.
|
|
46
|
+
*/
|
|
47
|
+
exclude?: boolean;
|
|
48
|
+
}>;
|
|
49
|
+
}>;
|
|
50
|
+
export type AgnosticRoute<T> = Readonly<{
|
|
51
|
+
path: string;
|
|
52
|
+
module?: T;
|
|
53
|
+
children?: ReadonlyArray<AgnosticRoute<T>>;
|
|
54
|
+
}>;
|
|
55
|
+
export declare function transformRoute<T, U>(route: T, getChildren: (route: T) => IterableIterator<T> | null | undefined, transformer: (route: T, children: readonly U[]) => U): U;
|
|
56
|
+
export declare function extractComponentName(component?: unknown): string | undefined;
|
|
57
|
+
export declare function adjustViewTitle(config?: ViewConfig, componentName?: string): ViewConfig | undefined;
|
|
58
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/runtime/utils.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,IAAI,CAAC,EAAE,QAAQ,CAAC;QACd;;;WAGG;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QAEf;;;;WAIG;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QACf;;;WAGG;QACH,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC,CAAC;CACJ,CAAC,CAAC;AAEH,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,QAAQ,CAAC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,CAAC,CAAC;IACX,QAAQ,CAAC,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;CAC5C,CAAC,CAAC;AAEH,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EACjC,KAAK,EAAE,CAAC,EACR,WAAW,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EACjE,WAAW,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,KAAK,CAAC,GACnD,CAAC,CAOH;AAED,wBAAgB,oBAAoB,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAW5E;AAKD,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAanG"}
|
package/runtime/utils.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function transformRoute(route, getChildren, transformer) {
|
|
2
|
+
const children = getChildren(route);
|
|
3
|
+
return transformer(
|
|
4
|
+
route,
|
|
5
|
+
children ? Array.from(children, (child) => transformRoute(child, getChildren, transformer)) : []
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
function extractComponentName(component) {
|
|
9
|
+
if (component && (typeof component === "object" || typeof component === "function") && "name" in component && typeof component.name === "string") {
|
|
10
|
+
return component.name;
|
|
11
|
+
}
|
|
12
|
+
return void 0;
|
|
13
|
+
}
|
|
14
|
+
const viewPattern = /view/giu;
|
|
15
|
+
const upperCaseSplitPattern = /(?=[A-Z])/gu;
|
|
16
|
+
function adjustViewTitle(config, componentName) {
|
|
17
|
+
if (config?.title) {
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
20
|
+
if (componentName) {
|
|
21
|
+
return {
|
|
22
|
+
...config,
|
|
23
|
+
title: componentName.replace(viewPattern, "").split(upperCaseSplitPattern).join(" ")
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return config;
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
adjustViewTitle,
|
|
30
|
+
extractComponentName,
|
|
31
|
+
transformRoute
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/runtime/utils.ts"],
|
|
4
|
+
"sourcesContent": ["export type ViewConfig = Readonly<{\n /**\n * View title used in the main layout header, as <title> and as the default\n * for the menu entry. If not defined, then the view function name is converted\n * from CamelCase after removing any \"View\" postfix.\n */\n title?: string;\n\n /**\n * Same as in the explicit React Router configuration.\n */\n rolesAllowed?: string[];\n\n /**\n * Allows overriding the route path configuration. Uses the same syntax as\n * the path property with React Router. This can be used to define a route\n * that conflicts with the file name conventions, e.g. /foo/index\n */\n route?: string;\n\n /**\n * Controls whether the view implementation will be lazy loaded the first time\n * it's used or always included in the bundle. If set to undefined (which is\n * the default), views mapped to / and /login will be eager and any other view\n * will be lazy (this is in sync with defaults in Flow)\n */\n lazy?: boolean;\n\n /**\n * If set to false, then the route will not be registered with React Router,\n * but it will still be included in the main menu and used to configure\n * Spring Security\n */\n register?: boolean;\n\n menu?: Readonly<{\n /**\n * Title to use in the menu. Falls back the title property of the view\n * itself if not defined.\n */\n title?: string;\n\n /**\n * Used to determine the order in the menu. Ties are resolved based on the\n * used title. Entries without explicitly defined ordering are put below\n * entries with an order.\n */\n order?: number;\n /**\n * Set to true to explicitly exclude a view from the automatically\n * populated menu.\n */\n exclude?: boolean;\n }>;\n}>;\n\nexport type AgnosticRoute<T> = Readonly<{\n path: string;\n module?: T;\n children?: ReadonlyArray<AgnosticRoute<T>>;\n}>;\n\nexport function transformRoute<T, U>(\n route: T,\n getChildren: (route: T) => IterableIterator<T> | null | undefined,\n transformer: (route: T, children: readonly U[]) => U,\n): U {\n const children = getChildren(route);\n\n return transformer(\n route,\n children ? Array.from(children, (child) => transformRoute(child, getChildren, transformer)) : [],\n );\n}\n\nexport function extractComponentName(component?: unknown): string | undefined {\n if (\n component &&\n (typeof component === 'object' || typeof component === 'function') &&\n 'name' in component &&\n typeof component.name === 'string'\n ) {\n return component.name;\n }\n\n return undefined;\n}\n\nconst viewPattern = /view/giu;\nconst upperCaseSplitPattern = /(?=[A-Z])/gu;\n\nexport function adjustViewTitle(config?: ViewConfig, componentName?: string): ViewConfig | undefined {\n if (config?.title) {\n return config;\n }\n\n if (componentName) {\n return {\n ...config,\n title: componentName.replace(viewPattern, '').split(upperCaseSplitPattern).join(' '),\n };\n }\n\n return config;\n}\n"],
|
|
5
|
+
"mappings": "AA8DO,SAAS,eACd,OACA,aACA,aACG;AACH,QAAM,WAAW,YAAY,KAAK;AAElC,SAAO;AAAA,IACL;AAAA,IACA,WAAW,MAAM,KAAK,UAAU,CAAC,UAAU,eAAe,OAAO,aAAa,WAAW,CAAC,IAAI,CAAC;AAAA,EACjG;AACF;AAEO,SAAS,qBAAqB,WAAyC;AAC5E,MACE,cACC,OAAO,cAAc,YAAY,OAAO,cAAc,eACvD,UAAU,aACV,OAAO,UAAU,SAAS,UAC1B;AACA,WAAO,UAAU;AAAA,EACnB;AAEA,SAAO;AACT;AAEA,MAAM,cAAc;AACpB,MAAM,wBAAwB;AAEvB,SAAS,gBAAgB,QAAqB,eAAgD;AACnG,MAAI,QAAQ,OAAO;AACjB,WAAO;AAAA,EACT;AAEA,MAAI,eAAe;AACjB,WAAO;AAAA,MACL,GAAG;AAAA,MACH,OAAO,cAAc,QAAQ,aAAa,EAAE,EAAE,MAAM,qBAAqB,EAAE,KAAK,GAAG;AAAA,IACrF;AAAA,EACF;AAEA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/runtime.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ComponentType } from 'react';
|
|
2
|
+
import { type RouteObject } from 'react-router';
|
|
3
|
+
import { type AgnosticRoute, type ViewConfig } from './runtime/utils.js';
|
|
4
|
+
export type RouteModule<P = object> = Readonly<{
|
|
5
|
+
default: ComponentType<P>;
|
|
6
|
+
config?: ViewConfig;
|
|
7
|
+
}>;
|
|
8
|
+
/**
|
|
9
|
+
* Transforms generated routes into a format that can be used by React Router.
|
|
10
|
+
*
|
|
11
|
+
* @param routes - Generated routes
|
|
12
|
+
*/
|
|
13
|
+
export declare function toReactRouter(routes: AgnosticRoute<RouteModule>): RouteObject;
|
|
14
|
+
/**
|
|
15
|
+
* Hook to return the {@link ViewConfig} for the current route.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useViewConfig<M extends ViewConfig>(): M | undefined;
|
|
18
|
+
//# sourceMappingURL=runtime.d.ts.map
|
package/runtime.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["src/runtime.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAiB,MAAM,OAAO,CAAC;AAC1D,OAAO,EAAE,KAAK,WAAW,EAAc,MAAM,cAAc,CAAC;AAC5D,OAAO,EACL,KAAK,aAAa,EAGlB,KAAK,UAAU,EAEhB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,MAAM,IAAI,QAAQ,CAAC;IAC7C,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB,CAAC,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,GAAG,WAAW,CAY7E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,UAAU,KAAK,CAAC,GAAG,SAAS,CAGnE"}
|
package/runtime.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { useMatches } from "react-router";
|
|
3
|
+
import {
|
|
4
|
+
transformRoute,
|
|
5
|
+
adjustViewTitle,
|
|
6
|
+
extractComponentName
|
|
7
|
+
} from "./runtime/utils.js";
|
|
8
|
+
function toReactRouter(routes) {
|
|
9
|
+
return transformRoute(
|
|
10
|
+
routes,
|
|
11
|
+
(route) => route.children?.values(),
|
|
12
|
+
({ path, module }, children) => ({
|
|
13
|
+
path,
|
|
14
|
+
element: module?.default ? createElement(module.default) : void 0,
|
|
15
|
+
children: children.length > 0 ? children : void 0,
|
|
16
|
+
handle: adjustViewTitle(module?.config, extractComponentName(module?.default))
|
|
17
|
+
})
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
function useViewConfig() {
|
|
21
|
+
const matches = useMatches();
|
|
22
|
+
return matches[matches.length - 1]?.handle;
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
toReactRouter,
|
|
26
|
+
useViewConfig
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=runtime.js.map
|
package/runtime.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["src/runtime.ts"],
|
|
4
|
+
"sourcesContent": ["import type { UIMatch } from '@remix-run/router';\nimport { type ComponentType, createElement } from 'react';\nimport { type RouteObject, useMatches } from 'react-router';\nimport {\n type AgnosticRoute,\n transformRoute,\n adjustViewTitle,\n type ViewConfig,\n extractComponentName,\n} from './runtime/utils.js';\n\nexport type RouteModule<P = object> = Readonly<{\n default: ComponentType<P>;\n config?: ViewConfig;\n}>;\n\n/**\n * Transforms generated routes into a format that can be used by React Router.\n *\n * @param routes - Generated routes\n */\nexport function toReactRouter(routes: AgnosticRoute<RouteModule>): RouteObject {\n return transformRoute(\n routes,\n (route) => route.children?.values(),\n ({ path, module }, children) =>\n ({\n path,\n element: module?.default ? createElement(module.default) : undefined,\n children: children.length > 0 ? (children as RouteObject[]) : undefined,\n handle: adjustViewTitle(module?.config, extractComponentName(module?.default)),\n }) satisfies RouteObject,\n );\n}\n\n/**\n * Hook to return the {@link ViewConfig} for the current route.\n */\nexport function useViewConfig<M extends ViewConfig>(): M | undefined {\n const matches = useMatches() as ReadonlyArray<UIMatch<unknown, M>>;\n return matches[matches.length - 1]?.handle;\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAA6B,qBAAqB;AAClD,SAA2B,kBAAkB;AAC7C;AAAA,EAEE;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AAYA,SAAS,cAAc,QAAiD;AAC7E,SAAO;AAAA,IACL;AAAA,IACA,CAAC,UAAU,MAAM,UAAU,OAAO;AAAA,IAClC,CAAC,EAAE,MAAM,OAAO,GAAG,cAChB;AAAA,MACC;AAAA,MACA,SAAS,QAAQ,UAAU,cAAc,OAAO,OAAO,IAAI;AAAA,MAC3D,UAAU,SAAS,SAAS,IAAK,WAA6B;AAAA,MAC9D,QAAQ,gBAAgB,QAAQ,QAAQ,qBAAqB,QAAQ,OAAO,CAAC;AAAA,IAC/E;AAAA,EACJ;AACF;AAKO,SAAS,gBAAqD;AACnE,QAAM,UAAU,WAAW;AAC3B,SAAO,QAAQ,QAAQ,SAAS,CAAC,GAAG;AACtC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type RouteMeta = Readonly<{
|
|
2
|
+
path: string;
|
|
3
|
+
file?: URL;
|
|
4
|
+
layout?: URL;
|
|
5
|
+
children: RouteMeta[];
|
|
6
|
+
}>;
|
|
7
|
+
export type CollectRoutesOptions = Readonly<{
|
|
8
|
+
extensions: readonly string[];
|
|
9
|
+
parent?: URL;
|
|
10
|
+
}>;
|
|
11
|
+
export default function collectRoutesFromFS(dir: URL, { extensions, parent }: CollectRoutesOptions): Promise<RouteMeta>;
|
|
12
|
+
//# sourceMappingURL=collectRoutesFromFS.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collectRoutesFromFS.d.ts","sourceRoot":"","sources":["../src/vite-plugin/collectRoutesFromFS.ts"],"names":[],"mappings":"AAKA,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,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,MAAM,CAAC,EAAE,GAAG,CAAC;CACd,CAAC,CAAC;AAIH,wBAA8B,mBAAmB,CAC/C,GAAG,EAAE,GAAG,EACR,EAAE,UAAU,EAAE,MAAY,EAAE,EAAE,oBAAoB,GACjD,OAAO,CAAC,SAAS,CAAC,CAuCpB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { opendir } from "node:fs/promises";
|
|
2
|
+
import { basename, extname, relative } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { cleanUp } from "./utils.js";
|
|
5
|
+
const collator = new Intl.Collator("en-US");
|
|
6
|
+
async function collectRoutesFromFS(dir, { extensions, parent = dir }) {
|
|
7
|
+
const path = relative(fileURLToPath(parent), fileURLToPath(dir));
|
|
8
|
+
const children = [];
|
|
9
|
+
let layout;
|
|
10
|
+
for await (const d of await opendir(dir)) {
|
|
11
|
+
if (d.isDirectory()) {
|
|
12
|
+
children.push(await collectRoutesFromFS(new URL(`${d.name}/`, dir), { extensions, parent: dir }));
|
|
13
|
+
} else if (d.isFile() && extensions.includes(extname(d.name))) {
|
|
14
|
+
const file = new URL(d.name, dir);
|
|
15
|
+
const name = basename(d.name, extname(d.name));
|
|
16
|
+
if (name.startsWith("$")) {
|
|
17
|
+
if (name === "$layout") {
|
|
18
|
+
layout = file;
|
|
19
|
+
} else if (name === "$index") {
|
|
20
|
+
children.push({
|
|
21
|
+
path: "",
|
|
22
|
+
file,
|
|
23
|
+
children: []
|
|
24
|
+
});
|
|
25
|
+
} else {
|
|
26
|
+
throw new Error('Symbol "$" is reserved for special files; only "$layout" and "$index" are allowed');
|
|
27
|
+
}
|
|
28
|
+
} else if (!name.startsWith("_")) {
|
|
29
|
+
children.push({
|
|
30
|
+
path: name,
|
|
31
|
+
file,
|
|
32
|
+
children: []
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
path,
|
|
39
|
+
layout,
|
|
40
|
+
children: children.sort(({ path: a }, { path: b }) => collator.compare(cleanUp(a), cleanUp(b)))
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
collectRoutesFromFS as default
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=collectRoutesFromFS.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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 { 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\nexport default async function collectRoutesFromFS(\n dir: URL,\n { extensions, parent = dir }: CollectRoutesOptions,\n): Promise<RouteMeta> {\n const path = relative(fileURLToPath(parent), fileURLToPath(dir));\n const 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, 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 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,eAAe;AACxB,SAAS,UAAU,SAAS,gBAAgB;AAC5C,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AAcxB,MAAM,WAAW,IAAI,KAAK,SAAS,OAAO;AAE1C,eAAO,oBACL,KACA,EAAE,YAAY,SAAS,IAAI,GACP;AACpB,QAAM,OAAO,SAAS,cAAc,MAAM,GAAG,cAAc,GAAG,CAAC;AAC/D,QAAM,WAAwB,CAAC;AAC/B,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,IAAI,CAAC,CAAC;AAAA,IAClG,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,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
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createRoutesFromMeta.d.ts","sourceRoot":"","sources":["../src/vite-plugin/createRoutesFromMeta.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAyC1D,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,GAAG,MAAM,CA0CxF"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { extname, relative } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { template, transform as transformer } from "@vaadin/hilla-generator-utils/ast.js";
|
|
4
|
+
import createSourceFile from "@vaadin/hilla-generator-utils/createSourceFile.js";
|
|
5
|
+
import ts, {
|
|
6
|
+
} from "typescript";
|
|
7
|
+
import { transformRoute } from "../runtime/utils.js";
|
|
8
|
+
import { convertFSPatternToURLPatternString } from "./utils.js";
|
|
9
|
+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
10
|
+
function relativize(url, generatedDir) {
|
|
11
|
+
const result = relative(fileURLToPath(generatedDir), fileURLToPath(url));
|
|
12
|
+
if (!result.startsWith(".")) {
|
|
13
|
+
return `./${result}`;
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
function createImport(mod, file) {
|
|
18
|
+
const path = `${file.substring(0, file.lastIndexOf("."))}.js`;
|
|
19
|
+
return template(`import * as ${mod} from '${path}';
|
|
20
|
+
`, ([statement]) => statement);
|
|
21
|
+
}
|
|
22
|
+
function createRouteData(path, mod, children) {
|
|
23
|
+
return template(
|
|
24
|
+
`const route = {
|
|
25
|
+
path: '${path}',
|
|
26
|
+
${mod ? `module: ${mod}` : ""}
|
|
27
|
+
${children.length > 0 ? `children: CHILDREN,` : ""}
|
|
28
|
+
}`,
|
|
29
|
+
([statement]) => statement.declarationList.declarations[0].initializer,
|
|
30
|
+
[
|
|
31
|
+
transformer(
|
|
32
|
+
(node) => ts.isIdentifier(node) && node.text === "CHILDREN" ? ts.factory.createArrayLiteralExpression(children) : node
|
|
33
|
+
)
|
|
34
|
+
]
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function createRoutesFromMeta(views, generatedDir) {
|
|
38
|
+
const imports = [];
|
|
39
|
+
let id = 0;
|
|
40
|
+
const routes = transformRoute(
|
|
41
|
+
views,
|
|
42
|
+
(view) => view.children.values(),
|
|
43
|
+
({ file: file2, layout, path }, children) => {
|
|
44
|
+
const currentId = id;
|
|
45
|
+
id += 1;
|
|
46
|
+
let mod;
|
|
47
|
+
if (file2) {
|
|
48
|
+
mod = `Page${currentId}`;
|
|
49
|
+
imports.push(createImport(mod, relativize(file2, generatedDir)));
|
|
50
|
+
} else if (layout) {
|
|
51
|
+
mod = `Layout${currentId}`;
|
|
52
|
+
imports.push(createImport(mod, relativize(layout, generatedDir)));
|
|
53
|
+
}
|
|
54
|
+
return createRouteData(convertFSPatternToURLPatternString(path), mod, children);
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
const routeDeclaration = template(
|
|
58
|
+
`import a from 'IMPORTS';
|
|
59
|
+
|
|
60
|
+
const routes = ROUTE;
|
|
61
|
+
|
|
62
|
+
export default routes;
|
|
63
|
+
`,
|
|
64
|
+
[
|
|
65
|
+
transformer(
|
|
66
|
+
(node) => ts.isImportDeclaration(node) && node.moduleSpecifier.text === "IMPORTS" ? imports : node
|
|
67
|
+
),
|
|
68
|
+
transformer((node) => ts.isIdentifier(node) && node.text === "ROUTE" ? routes : node)
|
|
69
|
+
]
|
|
70
|
+
);
|
|
71
|
+
const file = createSourceFile(routeDeclaration, "views.ts");
|
|
72
|
+
return printer.printFile(file);
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
createRoutesFromMeta as default
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=createRoutesFromMeta.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/vite-plugin/createRoutesFromMeta.ts"],
|
|
4
|
+
"sourcesContent": ["import { extname, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { template, transform as transformer } from '@vaadin/hilla-generator-utils/ast.js';\nimport createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';\nimport ts, {\n type ImportDeclaration,\n type ObjectLiteralExpression,\n type StringLiteral,\n type VariableStatement,\n} from 'typescript';\nimport { transformRoute } from '../runtime/utils.js';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\nimport { convertFSPatternToURLPatternString } from './utils.js';\n\nconst printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });\n\nfunction relativize(url: URL, generatedDir: URL): string {\n const result = relative(fileURLToPath(generatedDir), fileURLToPath(url));\n\n if (!result.startsWith('.')) {\n return `./${result}`;\n }\n\n return result;\n}\n\nfunction createImport(mod: string, file: string): ImportDeclaration {\n const path = `${file.substring(0, file.lastIndexOf('.'))}.js`;\n return template(`import * as ${mod} from '${path}';\\n`, ([statement]) => statement as ts.ImportDeclaration);\n}\n\nfunction createRouteData(\n path: string,\n mod: string | undefined,\n children: readonly ObjectLiteralExpression[],\n): ObjectLiteralExpression {\n return template(\n `const route = {\n path: '${path}',\n ${mod ? `module: ${mod}` : ''}\n ${children.length > 0 ? `children: CHILDREN,` : ''}\n}`,\n ([statement]) =>\n (statement as VariableStatement).declarationList.declarations[0].initializer as ObjectLiteralExpression,\n [\n transformer((node) =>\n ts.isIdentifier(node) && node.text === 'CHILDREN' ? ts.factory.createArrayLiteralExpression(children) : node,\n ),\n ],\n );\n}\n\nexport default function createRoutesFromMeta(views: RouteMeta, generatedDir: URL): string {\n const imports: ImportDeclaration[] = [];\n let id = 0;\n\n const routes = transformRoute<RouteMeta, ObjectLiteralExpression>(\n views,\n (view) => view.children.values(),\n ({ file, layout, path }, children) => {\n const currentId = id;\n id += 1;\n\n let mod: string | undefined;\n if (file) {\n mod = `Page${currentId}`;\n imports.push(createImport(mod, relativize(file, generatedDir)));\n } else if (layout) {\n mod = `Layout${currentId}`;\n imports.push(createImport(mod, relativize(layout, generatedDir)));\n }\n\n return createRouteData(convertFSPatternToURLPatternString(path), mod, children);\n },\n );\n\n const routeDeclaration = template(\n `import a from 'IMPORTS';\n\nconst routes = ROUTE;\n\nexport default routes;\n`,\n [\n transformer((node) =>\n ts.isImportDeclaration(node) && (node.moduleSpecifier as StringLiteral).text === 'IMPORTS' ? imports : node,\n ),\n transformer((node) => (ts.isIdentifier(node) && node.text === 'ROUTE' ? routes : node)),\n ],\n );\n\n const file = createSourceFile(routeDeclaration, 'views.ts');\n\n return printer.printFile(file);\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS,gBAAgB;AAClC,SAAS,qBAAqB;AAC9B,SAAS,UAAU,aAAa,mBAAmB;AACnD,OAAO,sBAAsB;AAC7B,OAAO;AAAA,OAKA;AACP,SAAS,sBAAsB;AAE/B,SAAS,0CAA0C;AAEnD,MAAM,UAAU,GAAG,cAAc,EAAE,SAAS,GAAG,YAAY,SAAS,CAAC;AAErE,SAAS,WAAW,KAAU,cAA2B;AACvD,QAAM,SAAS,SAAS,cAAc,YAAY,GAAG,cAAc,GAAG,CAAC;AAEvE,MAAI,CAAC,OAAO,WAAW,GAAG,GAAG;AAC3B,WAAO,KAAK,MAAM;AAAA,EACpB;AAEA,SAAO;AACT;AAEA,SAAS,aAAa,KAAa,MAAiC;AAClE,QAAM,OAAO,GAAG,KAAK,UAAU,GAAG,KAAK,YAAY,GAAG,CAAC,CAAC;AACxD,SAAO,SAAS,eAAe,GAAG,UAAU,IAAI;AAAA,GAAQ,CAAC,CAAC,SAAS,MAAM,SAAiC;AAC5G;AAEA,SAAS,gBACP,MACA,KACA,UACyB;AACzB,SAAO;AAAA,IACL;AAAA,WACO,IAAI;AAAA,IACX,MAAM,WAAW,GAAG,KAAK,EAAE;AAAA,IAC3B,SAAS,SAAS,IAAI,wBAAwB,EAAE;AAAA;AAAA,IAEhD,CAAC,CAAC,SAAS,MACR,UAAgC,gBAAgB,aAAa,CAAC,EAAE;AAAA,IACnE;AAAA,MACE;AAAA,QAAY,CAAC,SACX,GAAG,aAAa,IAAI,KAAK,KAAK,SAAS,aAAa,GAAG,QAAQ,6BAA6B,QAAQ,IAAI;AAAA,MAC1G;AAAA,IACF;AAAA,EACF;AACF;AAEe,SAAR,qBAAsC,OAAkB,cAA2B;AACxF,QAAM,UAA+B,CAAC;AACtC,MAAI,KAAK;AAET,QAAM,SAAS;AAAA,IACb;AAAA,IACA,CAAC,SAAS,KAAK,SAAS,OAAO;AAAA,IAC/B,CAAC,EAAE,MAAAA,OAAM,QAAQ,KAAK,GAAG,aAAa;AACpC,YAAM,YAAY;AAClB,YAAM;AAEN,UAAI;AACJ,UAAIA,OAAM;AACR,cAAM,OAAO,SAAS;AACtB,gBAAQ,KAAK,aAAa,KAAK,WAAWA,OAAM,YAAY,CAAC,CAAC;AAAA,MAChE,WAAW,QAAQ;AACjB,cAAM,SAAS,SAAS;AACxB,gBAAQ,KAAK,aAAa,KAAK,WAAW,QAAQ,YAAY,CAAC,CAAC;AAAA,MAClE;AAEA,aAAO,gBAAgB,mCAAmC,IAAI,GAAG,KAAK,QAAQ;AAAA,IAChF;AAAA,EACF;AAEA,QAAM,mBAAmB;AAAA,IACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA;AAAA,MACE;AAAA,QAAY,CAAC,SACX,GAAG,oBAAoB,IAAI,KAAM,KAAK,gBAAkC,SAAS,YAAY,UAAU;AAAA,MACzG;AAAA,MACA,YAAY,CAAC,SAAU,GAAG,aAAa,IAAI,KAAK,KAAK,SAAS,UAAU,SAAS,IAAK;AAAA,IACxF;AAAA,EACF;AAEA,QAAM,OAAO,iBAAiB,kBAAkB,UAAU;AAE1D,SAAO,QAAQ,UAAU,IAAI;AAC/B;",
|
|
6
|
+
"names": ["file"]
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createViewConfigJson.d.ts","sourceRoot":"","sources":["../src/vite-plugin/createViewConfigJson.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AA0B1D,wBAA8B,oBAAoB,CAAC,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuC9G"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { Script } from "node:vm";
|
|
3
|
+
import ts, {} from "typescript";
|
|
4
|
+
import { adjustViewTitle } from "../runtime/utils.js";
|
|
5
|
+
import { convertFSPatternToURLPatternString } from "./utils.js";
|
|
6
|
+
function* traverse(views, parents = []) {
|
|
7
|
+
const chain = [...parents, views];
|
|
8
|
+
if (views.children.length === 0) {
|
|
9
|
+
yield chain;
|
|
10
|
+
}
|
|
11
|
+
for (const child of views.children) {
|
|
12
|
+
yield* traverse(child, chain);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function* walkAST(node) {
|
|
16
|
+
yield node;
|
|
17
|
+
for (const child of node.getChildren()) {
|
|
18
|
+
yield* walkAST(child);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function createViewConfigJson(views, configExportName) {
|
|
22
|
+
const res = await Promise.all(
|
|
23
|
+
Array.from(traverse(views), async (branch) => {
|
|
24
|
+
const configs = await Promise.all(
|
|
25
|
+
branch.filter(({ file, layout }) => !!file || !!layout).map(({ file, layout }) => file ?? layout).map(async (path) => {
|
|
26
|
+
const file = ts.createSourceFile("f.ts", await readFile(path, "utf8"), ts.ScriptTarget.ESNext, true);
|
|
27
|
+
let config;
|
|
28
|
+
let waitingForIdentifier = false;
|
|
29
|
+
let componentName;
|
|
30
|
+
for (const node of walkAST(file)) {
|
|
31
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === configExportName) {
|
|
32
|
+
if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {
|
|
33
|
+
const code = node.initializer.getText(file);
|
|
34
|
+
const script = new Script(`(${code})`);
|
|
35
|
+
config = script.runInThisContext();
|
|
36
|
+
}
|
|
37
|
+
} else if (node.getText(file).includes("export default")) {
|
|
38
|
+
waitingForIdentifier = true;
|
|
39
|
+
} else if (waitingForIdentifier && ts.isIdentifier(node)) {
|
|
40
|
+
componentName = node.text;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return adjustViewTitle(config, componentName);
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
const key = branch.map(({ path }) => convertFSPatternToURLPatternString(path)).join("/");
|
|
47
|
+
const value = configs[configs.length - 1];
|
|
48
|
+
return [key, value];
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
return JSON.stringify(Object.fromEntries(res));
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
createViewConfigJson as default
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=createViewConfigJson.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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 { adjustViewTitle, type ViewConfig } from '../runtime/utils.js';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\nimport { convertFSPatternToURLPatternString } from './utils.js';\n\nfunction* traverse(\n views: RouteMeta,\n parents: readonly RouteMeta[] = [],\n): Generator<readonly RouteMeta[], undefined, undefined> {\n const chain = [...parents, views];\n\n if (views.children.length === 0) {\n yield chain;\n }\n\n for (const child of views.children) {\n yield* traverse(child, chain);\n }\n}\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\nexport default async function createViewConfigJson(views: RouteMeta, configExportName: string): Promise<string> {\n const res = await Promise.all(\n Array.from(traverse(views), async (branch) => {\n const configs = await Promise.all(\n branch\n .filter(({ file, layout }) => !!file || !!layout)\n .map(({ file, layout }) => file ?? layout!)\n .map(async (path) => {\n const file = ts.createSourceFile('f.ts', await readFile(path, 'utf8'), ts.ScriptTarget.ESNext, true);\n let config: ViewConfig | undefined;\n let waitingForIdentifier = false;\n let componentName: string | undefined;\n\n for (const node of walkAST(file)) {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === configExportName) {\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const code = node.initializer.getText(file);\n const script = new Script(`(${code})`);\n config = script.runInThisContext() as ViewConfig;\n }\n } else if (node.getText(file).includes('export default')) {\n waitingForIdentifier = true;\n } else if (waitingForIdentifier && ts.isIdentifier(node)) {\n componentName = node.text;\n }\n }\n\n return adjustViewTitle(config, componentName);\n }),\n );\n\n const key = branch.map(({ path }) => convertFSPatternToURLPatternString(path)).join('/');\n const value = configs[configs.length - 1];\n\n return [key, value] satisfies readonly [string, ViewConfig | undefined];\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,uBAAwC;AAEjD,SAAS,0CAA0C;AAEnD,UAAU,SACR,OACA,UAAgC,CAAC,GACsB;AACvD,QAAM,QAAQ,CAAC,GAAG,SAAS,KAAK;AAEhC,MAAI,MAAM,SAAS,WAAW,GAAG;AAC/B,UAAM;AAAA,EACR;AAEA,aAAW,SAAS,MAAM,UAAU;AAClC,WAAO,SAAS,OAAO,KAAK;AAAA,EAC9B;AACF;AAEA,UAAU,QAAQ,MAA6B;AAC7C,QAAM;AAEN,aAAW,SAAS,KAAK,YAAY,GAAG;AACtC,WAAO,QAAQ,KAAK;AAAA,EACtB;AACF;AAEA,eAAO,qBAA4C,OAAkB,kBAA2C;AAC9G,QAAM,MAAM,MAAM,QAAQ;AAAA,IACxB,MAAM,KAAK,SAAS,KAAK,GAAG,OAAO,WAAW;AAC5C,YAAM,UAAU,MAAM,QAAQ;AAAA,QAC5B,OACG,OAAO,CAAC,EAAE,MAAM,OAAO,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,EAC/C,IAAI,CAAC,EAAE,MAAM,OAAO,MAAM,QAAQ,MAAO,EACzC,IAAI,OAAO,SAAS;AACnB,gBAAM,OAAO,GAAG,iBAAiB,QAAQ,MAAM,SAAS,MAAM,MAAM,GAAG,GAAG,aAAa,QAAQ,IAAI;AACnG,cAAI;AACJ,cAAI,uBAAuB;AAC3B,cAAI;AAEJ,qBAAW,QAAQ,QAAQ,IAAI,GAAG;AAChC,gBAAI,GAAG,sBAAsB,IAAI,KAAK,GAAG,aAAa,KAAK,IAAI,KAAK,KAAK,KAAK,SAAS,kBAAkB;AACvG,kBAAI,KAAK,eAAe,GAAG,0BAA0B,KAAK,WAAW,GAAG;AACtE,sBAAM,OAAO,KAAK,YAAY,QAAQ,IAAI;AAC1C,sBAAM,SAAS,IAAI,OAAO,IAAI,IAAI,GAAG;AACrC,yBAAS,OAAO,iBAAiB;AAAA,cACnC;AAAA,YACF,WAAW,KAAK,QAAQ,IAAI,EAAE,SAAS,gBAAgB,GAAG;AACxD,qCAAuB;AAAA,YACzB,WAAW,wBAAwB,GAAG,aAAa,IAAI,GAAG;AACxD,8BAAgB,KAAK;AAAA,YACvB;AAAA,UACF;AAEA,iBAAO,gBAAgB,QAAQ,aAAa;AAAA,QAC9C,CAAC;AAAA,MACL;AAEA,YAAM,MAAM,OAAO,IAAI,CAAC,EAAE,KAAK,MAAM,mCAAmC,IAAI,CAAC,EAAE,KAAK,GAAG;AACvF,YAAM,QAAQ,QAAQ,QAAQ,SAAS,CAAC;AAExC,aAAO,CAAC,KAAK,KAAK;AAAA,IACpB,CAAC;AAAA,EACH;AAEA,SAAO,KAAK,UAAU,OAAO,YAAY,GAAG,CAAC;AAC/C;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a file system pattern to a URL pattern string.
|
|
3
|
+
*
|
|
4
|
+
* @param fsPattern - a string representing a file system pattern:
|
|
5
|
+
* - `{param}` - for a required single parameter;
|
|
6
|
+
* - `{{param}}` - for an optional single parameter;
|
|
7
|
+
* - `{...rest}` - for multiple parameters, including none.
|
|
8
|
+
*
|
|
9
|
+
* @returns a string representing a URL pattern, respectively:
|
|
10
|
+
* - `:param`;
|
|
11
|
+
* - `:param?`;
|
|
12
|
+
* - `*`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function convertFSPatternToURLPatternString(fsPattern: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* A small helper function that clears route path of the control characters in
|
|
17
|
+
* order to sort the routes alphabetically.
|
|
18
|
+
*/
|
|
19
|
+
export declare function cleanUp(path: string): string;
|
|
20
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/vite-plugin/utils.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AACH,wBAAgB,kCAAkC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAU5E;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const restParamPattern = /\{\.{3}(.+)\}/gu;
|
|
2
|
+
const optionalParamPattern = /\{{2}(.+)\}{2}/gu;
|
|
3
|
+
const paramPattern = /\{(.+)\}/gu;
|
|
4
|
+
function convertFSPatternToURLPatternString(fsPattern) {
|
|
5
|
+
return fsPattern.replaceAll(restParamPattern, "*").replaceAll(optionalParamPattern, ":$1?").replaceAll(paramPattern, ":$1");
|
|
6
|
+
}
|
|
7
|
+
function cleanUp(path) {
|
|
8
|
+
return path.replaceAll(restParamPattern, "$1").replaceAll(optionalParamPattern, "$1").replaceAll(paramPattern, "$1");
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
cleanUp,
|
|
12
|
+
convertFSPatternToURLPatternString
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/vite-plugin/utils.ts"],
|
|
4
|
+
"sourcesContent": ["const restParamPattern = /\\{\\.{3}(.+)\\}/gu;\nconst optionalParamPattern = /\\{{2}(.+)\\}{2}/gu;\nconst paramPattern = /\\{(.+)\\}/gu;\n\n/**\n * Converts a file system pattern to a URL pattern string.\n *\n * @param fsPattern - a string representing a file system pattern:\n * - `{param}` - for a required single parameter;\n * - `{{param}}` - for an optional single parameter;\n * - `{...rest}` - for multiple parameters, including none.\n *\n * @returns a string representing a URL pattern, respectively:\n * - `:param`;\n * - `:param?`;\n * - `*`.\n */\nexport function convertFSPatternToURLPatternString(fsPattern: string): string {\n return (\n fsPattern\n // /url/{...rest}/page -> /url/*/page\n .replaceAll(restParamPattern, '*')\n // /url/{{param}}/page -> /url/:param?/page\n .replaceAll(optionalParamPattern, ':$1?')\n // /url/{param}/page -> /url/:param/page\n .replaceAll(paramPattern, ':$1')\n );\n}\n\n/**\n * A small helper function that clears route path of the control characters in\n * order to sort the routes alphabetically.\n */\nexport function cleanUp(path: string): string {\n return path.replaceAll(restParamPattern, '$1').replaceAll(optionalParamPattern, '$1').replaceAll(paramPattern, '$1');\n}\n"],
|
|
5
|
+
"mappings": "AAAA,MAAM,mBAAmB;AACzB,MAAM,uBAAuB;AAC7B,MAAM,eAAe;AAed,SAAS,mCAAmC,WAA2B;AAC5E,SACE,UAEG,WAAW,kBAAkB,GAAG,EAEhC,WAAW,sBAAsB,MAAM,EAEvC,WAAW,cAAc,KAAK;AAErC;AAMO,SAAS,QAAQ,MAAsB;AAC5C,SAAO,KAAK,WAAW,kBAAkB,IAAI,EAAE,WAAW,sBAAsB,IAAI,EAAE,WAAW,cAAc,IAAI;AACrH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
export type PluginOptions = Readonly<{
|
|
3
|
+
/**
|
|
4
|
+
* The base directory for the router. The folders and files in this directory
|
|
5
|
+
* will be used as route paths.
|
|
6
|
+
*
|
|
7
|
+
* @defaultValue `frontend/views`
|
|
8
|
+
*/
|
|
9
|
+
viewsDir?: URL | string;
|
|
10
|
+
/**
|
|
11
|
+
* The directory where the generated view file will be stored.
|
|
12
|
+
*
|
|
13
|
+
* @defaultValue `frontend/generated`
|
|
14
|
+
*/
|
|
15
|
+
generatedDir?: URL | string;
|
|
16
|
+
/**
|
|
17
|
+
* The list of extensions that will be collected as routes of the file-based
|
|
18
|
+
* router.
|
|
19
|
+
*
|
|
20
|
+
* @defaultValue `['.tsx', '.jsx', '.ts', '.js']`
|
|
21
|
+
*/
|
|
22
|
+
extensions?: readonly string[];
|
|
23
|
+
/**
|
|
24
|
+
* The name of the export that will be used for the {@link ViewConfig} in the
|
|
25
|
+
* route file.
|
|
26
|
+
*
|
|
27
|
+
* @defaultValue `config`
|
|
28
|
+
*/
|
|
29
|
+
configExportName?: string;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* A Vite plugin that generates a router from the files in the specific directory.
|
|
33
|
+
*
|
|
34
|
+
* @param options - The plugin options.
|
|
35
|
+
* @returns A Vite plugin.
|
|
36
|
+
*/
|
|
37
|
+
export default function vitePluginFileSystemRouter({ viewsDir, generatedDir, extensions, configExportName, }?: PluginOptions): Plugin;
|
|
38
|
+
//# sourceMappingURL=vite-plugin-file-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-plugin-file-router.d.ts","sourceRoot":"","sources":["src/vite-plugin-file-router.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAKnC,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC;IACxB;;;;OAIG;IACH,YAAY,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC,CAAC;AAyBH;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,0BAA0B,CAAC,EACjD,QAA4B,EAC5B,YAAoC,EACpC,UAA2C,EAC3C,gBAA2B,GAC5B,GAAE,aAAkB,GAAG,MAAM,CAqC7B"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
+
import collectRoutesFromFS from "./vite-plugin/collectRoutesFromFS.js";
|
|
4
|
+
import createRoutesFromMeta from "./vite-plugin/createRoutesFromMeta.js";
|
|
5
|
+
import createViewConfigJson from "./vite-plugin/createViewConfigJson.js";
|
|
6
|
+
async function generateRuntimeFiles(code, json, urls) {
|
|
7
|
+
await Promise.all([writeFile(urls.json, json, "utf-8"), writeFile(urls.code, code, "utf-8")]);
|
|
8
|
+
}
|
|
9
|
+
async function build(viewsDir, outDir, generatedUrls, extensions, configExportName) {
|
|
10
|
+
const routeMeta = await collectRoutesFromFS(viewsDir, { extensions });
|
|
11
|
+
const runtimeRoutesCode = createRoutesFromMeta(routeMeta, outDir);
|
|
12
|
+
const viewConfigJson = await createViewConfigJson(routeMeta, configExportName);
|
|
13
|
+
await generateRuntimeFiles(runtimeRoutesCode, viewConfigJson, generatedUrls);
|
|
14
|
+
}
|
|
15
|
+
function vitePluginFileSystemRouter({
|
|
16
|
+
viewsDir = "frontend/views/",
|
|
17
|
+
generatedDir = "frontend/generated/",
|
|
18
|
+
extensions = [".tsx", ".jsx", ".ts", ".js"],
|
|
19
|
+
configExportName = "config"
|
|
20
|
+
} = {}) {
|
|
21
|
+
let _viewsDir;
|
|
22
|
+
let _generatedDir;
|
|
23
|
+
let _outDir;
|
|
24
|
+
let generatedUrls;
|
|
25
|
+
return {
|
|
26
|
+
name: "vite-plugin-file-router",
|
|
27
|
+
configResolved({ root, build: { outDir } }) {
|
|
28
|
+
const _root = pathToFileURL(root);
|
|
29
|
+
_viewsDir = new URL(viewsDir, _root);
|
|
30
|
+
_generatedDir = new URL(generatedDir, _root);
|
|
31
|
+
_outDir = pathToFileURL(outDir);
|
|
32
|
+
generatedUrls = {
|
|
33
|
+
json: new URL("views.json", _outDir),
|
|
34
|
+
code: new URL("views.ts", _generatedDir)
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
async buildStart() {
|
|
38
|
+
await build(_viewsDir, _generatedDir, generatedUrls, extensions, configExportName);
|
|
39
|
+
},
|
|
40
|
+
configureServer(server) {
|
|
41
|
+
const dir = fileURLToPath(_viewsDir);
|
|
42
|
+
const changeListener = (file) => {
|
|
43
|
+
if (!file.startsWith(dir)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
build(_viewsDir, _outDir, generatedUrls, extensions, configExportName).catch((error) => console.error(error));
|
|
47
|
+
};
|
|
48
|
+
server.watcher.on("add", changeListener);
|
|
49
|
+
server.watcher.on("change", changeListener);
|
|
50
|
+
server.watcher.on("unlink", changeListener);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
vitePluginFileSystemRouter as default
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=vite-plugin-file-router.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["src/vite-plugin-file-router.ts"],
|
|
4
|
+
"sourcesContent": ["import { writeFile } from 'node:fs/promises';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { Plugin } from 'vite';\nimport collectRoutesFromFS from './vite-plugin/collectRoutesFromFS.js';\nimport createRoutesFromMeta from './vite-plugin/createRoutesFromMeta.js';\nimport createViewConfigJson from './vite-plugin/createViewConfigJson.js';\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 * The name of the export that will be used for the {@link ViewConfig} in the\n * route file.\n *\n * @defaultValue `config`\n */\n configExportName?: string;\n}>;\n\ntype RuntimeFileUrls = Readonly<{\n json: URL;\n code: URL;\n}>;\n\nasync function generateRuntimeFiles(code: string, json: string, urls: RuntimeFileUrls) {\n await Promise.all([writeFile(urls.json, json, 'utf-8'), writeFile(urls.code, code, 'utf-8')]);\n}\n\nasync function build(\n viewsDir: URL,\n outDir: URL,\n generatedUrls: RuntimeFileUrls,\n extensions: readonly string[],\n configExportName: string,\n): Promise<void> {\n const routeMeta = await collectRoutesFromFS(viewsDir, { extensions });\n const runtimeRoutesCode = createRoutesFromMeta(routeMeta, outDir);\n const viewConfigJson = await createViewConfigJson(routeMeta, configExportName);\n\n await generateRuntimeFiles(runtimeRoutesCode, viewConfigJson, generatedUrls);\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 configExportName = 'config',\n}: PluginOptions = {}): Plugin {\n let _viewsDir: URL;\n let _generatedDir: URL;\n let _outDir: URL;\n let generatedUrls: RuntimeFileUrls;\n\n return {\n name: 'vite-plugin-file-router',\n configResolved({ root, build: { outDir } }) {\n const _root = pathToFileURL(root);\n _viewsDir = new URL(viewsDir, _root);\n _generatedDir = new URL(generatedDir, _root);\n _outDir = pathToFileURL(outDir);\n generatedUrls = {\n json: new URL('views.json', _outDir),\n code: new URL('views.ts', _generatedDir),\n };\n },\n async buildStart() {\n await build(_viewsDir, _generatedDir, generatedUrls, extensions, configExportName);\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 build(_viewsDir, _outDir, generatedUrls, extensions, configExportName).catch((error) => console.error(error));\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,iBAAiB;AAC1B,SAAS,eAAe,qBAAqB;AAE7C,OAAO,yBAAyB;AAChC,OAAO,0BAA0B;AACjC,OAAO,0BAA0B;AAqCjC,eAAe,qBAAqB,MAAc,MAAc,MAAuB;AACrF,QAAM,QAAQ,IAAI,CAAC,UAAU,KAAK,MAAM,MAAM,OAAO,GAAG,UAAU,KAAK,MAAM,MAAM,OAAO,CAAC,CAAC;AAC9F;AAEA,eAAe,MACb,UACA,QACA,eACA,YACA,kBACe;AACf,QAAM,YAAY,MAAM,oBAAoB,UAAU,EAAE,WAAW,CAAC;AACpE,QAAM,oBAAoB,qBAAqB,WAAW,MAAM;AAChE,QAAM,iBAAiB,MAAM,qBAAqB,WAAW,gBAAgB;AAE7E,QAAM,qBAAqB,mBAAmB,gBAAgB,aAAa;AAC7E;AAQe,SAAR,2BAA4C;AAAA,EACjD,WAAW;AAAA,EACX,eAAe;AAAA,EACf,aAAa,CAAC,QAAQ,QAAQ,OAAO,KAAK;AAAA,EAC1C,mBAAmB;AACrB,IAAmB,CAAC,GAAW;AAC7B,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,eAAe,EAAE,MAAM,OAAO,EAAE,OAAO,EAAE,GAAG;AAC1C,YAAM,QAAQ,cAAc,IAAI;AAChC,kBAAY,IAAI,IAAI,UAAU,KAAK;AACnC,sBAAgB,IAAI,IAAI,cAAc,KAAK;AAC3C,gBAAU,cAAc,MAAM;AAC9B,sBAAgB;AAAA,QACd,MAAM,IAAI,IAAI,cAAc,OAAO;AAAA,QACnC,MAAM,IAAI,IAAI,YAAY,aAAa;AAAA,MACzC;AAAA,IACF;AAAA,IACA,MAAM,aAAa;AACjB,YAAM,MAAM,WAAW,eAAe,eAAe,YAAY,gBAAgB;AAAA,IACnF;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,cAAM,WAAW,SAAS,eAAe,YAAY,gBAAgB,EAAE,MAAM,CAAC,UAAU,QAAQ,MAAM,KAAK,CAAC;AAAA,MAC9G;AAEA,aAAO,QAAQ,GAAG,OAAO,cAAc;AACvC,aAAO,QAAQ,GAAG,UAAU,cAAc;AAC1C,aAAO,QAAQ,GAAG,UAAU,cAAc;AAAA,IAC5C;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|