UNPKG

@vaadin/hilla-file-router

Version:

Hilla file-based router

131 lines 5.29 kB
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