@vaadin/hilla-file-router
Version:
Hilla file-based router
131 lines • 5.29 kB
JavaScript
import createSourceFile from "@vaadin/hilla-generator-utils/createSourceFile.js";
import DependencyManager from "@vaadin/hilla-generator-utils/dependencies/DependencyManager.js";
import PathManager from "@vaadin/hilla-generator-utils/dependencies/PathManager.js";
import ast from "tsc-template";
import { createPrinter, factory, NewLineKind } from "typescript";
import { transformTree } from "../shared/transformTree.js";
import { convertFSRouteSegmentToURLPatternFormat } from "./utils.js";
const printer = createPrinter({ newLine: NewLineKind.LineFeed });
const fileExtensions = [
".ts",
".tsx",
".js",
".jsx"
];
const HILLA_FILE_ROUTER = "@vaadin/hilla-file-router";
const HILLA_FILE_ROUTER_RUNTIME = `${HILLA_FILE_ROUTER}/runtime.js`;
const HILLA_FILE_ROUTER_TYPES = `${HILLA_FILE_ROUTER}/types.js`;
function isLazyModule(mod) {
return !!mod && "path" in mod;
}
function isLazyRoute(pathContext, path, config) {
if (config.lazy !== undefined) {
return config.lazy;
}
const rootContext = !pathContext.some(Boolean);
const eagerDefault = rootContext && (path === "" || path === "login");
return !eagerDefault;
}
class RouteFromMetaProcessor {
#manager;
#views;
#configs;
constructor(views, viewConfigs, { code: codeFile }) {
this.#views = views;
this.#configs = viewConfigs;
const codeDir = new URL("./", codeFile);
this.#manager = new DependencyManager(new PathManager({
extension: ".js",
relativeTo: codeDir
}));
}
/**
* Loads all the files from the received metadata and creates a framework-agnostic route tree.
*/
process() {
const { paths, imports: { namespace, named } } = this.#manager;
const errors = [];
const routes = transformTree([
[],
this.#views,
this.#configs
], null, ([pathContext, metas, configs], next) => {
errors.push(...metas.map((route) => route.path).filter((item, index, arr) => arr.indexOf(item) !== index).map((dup) => `console.error("Two views share the same path: ${dup}");`));
return metas.map(({ file, layout, path, children }, index) => {
const config = configs[index];
let _children;
if (children) {
_children = next([
[...pathContext, path],
children,
config.children
]);
}
let module;
let relativePath;
let fileName;
if (file) {
const fileExt = fileExtensions.find((ext) => file.pathname.endsWith(ext));
relativePath = paths.createRelativePath(file, fileExt);
fileName = "Page";
} else if (layout) {
const fileExt = fileExtensions.find((ext) => layout.pathname.endsWith(ext));
relativePath = paths.createRelativePath(layout, fileExt);
fileName = "Layout";
}
if (relativePath && fileName) {
if (isLazyRoute(pathContext, path, config)) {
module = {
path: relativePath,
config,
lazyId: named.getIdentifier("react", "lazy") ?? named.add("react", "lazy")
};
} else {
const mod = namespace.add(relativePath, fileName);
const reactModuleType = named.getIdentifier(HILLA_FILE_ROUTER_TYPES, "RouteModule") ?? named.add(HILLA_FILE_ROUTER_TYPES, "RouteModule", true);
module = {
componentId: ast`${mod}.default`.node,
configId: ast`(${mod} as ${reactModuleType}).config`.node,
flowLayout: config.flowLayout
};
}
}
return this.#createRouteData(convertFSRouteSegmentToURLPatternFormat(path), module, _children);
});
});
const agnosticRouteId = named.getIdentifier(HILLA_FILE_ROUTER_TYPES, "AgnosticRoute") ?? named.add(HILLA_FILE_ROUTER_TYPES, "AgnosticRoute", true);
const routeDeclaration = [...this.#manager.imports.toCode(), ...ast`${errors.join("\n")}
const routes: readonly ${agnosticRouteId}[] = ${factory.createArrayLiteralExpression(routes, true)};
export default routes;`.source.statements];
const file = createSourceFile(routeDeclaration, "file-routes.ts");
return printer.printFile(file);
}
/**
* Create an abstract route creation function call. The nested function calls
* create a route tree.
*
* @param path - The path of the route.
* @param module - The module to build route from.
* @param children - The list of child route call expressions.
*/
#createRouteData(path, module, children) {
const { named } = this.#manager.imports;
const createRouteId = named.getIdentifier(HILLA_FILE_ROUTER_RUNTIME, "createRoute") ?? named.add(HILLA_FILE_ROUTER_RUNTIME, "createRoute");
let component;
let config;
if (isLazyModule(module)) {
const { children: _c, params: _p,...viewConfig } = module.config;
component = ast`${module.lazyId}(() => import('${module.path}')`.node;
config = Object.keys(viewConfig).length > 0 ? JSON.stringify(viewConfig) : "";
} else if (module) {
component = module.componentId;
config = module.flowLayout ? ast`const a = %{ { ...${module.configId}, flowLayout: ${module.flowLayout.toString()} } }%`.node : module.configId;
}
const _children = children ? factory.createArrayLiteralExpression(children, true) : "";
return ast`${createRouteId}("${path}", ${component ?? ""}, ${config ?? ""}, ${_children})`.node;
}
}
export default function createRoutesFromMeta(views, viewConfigs, urls) {
return new RouteFromMetaProcessor(views, viewConfigs, urls).process();
}
//# sourceMappingURL=./createRoutesFromMeta.js.map