@vaadin/hilla-file-router
Version:
Hilla file-based router
95 lines (93 loc) • 4.67 kB
JavaScript
import { template, transform as transformer } from "@vaadin/hilla-generator-utils/ast.js";
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 ts from "typescript";
import { transformTree } from "../shared/transformTree.js";
import { convertFSRouteSegmentToURLPatternFormat } from "./utils.js";
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const fileExtensions = [
".ts",
".tsx",
".js",
".jsx"
];
class RouteFromMetaProcessor {
#manager;
#views;
constructor(views, { code: codeFile }) {
this.#views = views;
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.
*
* @param views - The abstract route tree.
* @param generatedDir - The directory where the generated view file will be stored.
*/
process() {
const { paths, imports: { named, namespace } } = this.#manager;
const errors = [];
const routes = transformTree(this.#views, null, (metas, 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, flowLayout }) => {
let _children;
if (children) {
_children = next(children);
}
let mod;
if (file) {
const fileExt = fileExtensions.find((ext) => file.pathname.endsWith(ext));
mod = namespace.add(paths.createRelativePath(file, fileExt), `Page`);
} else if (layout) {
const fileExt = fileExtensions.find((ext) => layout.pathname.endsWith(ext));
mod = namespace.add(paths.createRelativePath(layout, fileExt), `Layout`);
}
const moduleExtension = flowLayout ? { flowLayout } : undefined;
return this.#createRouteData(convertFSRouteSegmentToURLPatternFormat(path), mod, moduleExtension, _children);
});
});
const agnosticRouteId = named.getIdentifier("@vaadin/hilla-file-router/types.js", "AgnosticRoute") ?? named.add("@vaadin/hilla-file-router/types.js", "AgnosticRoute", true);
let routeDeclaration = template(`${errors.join("\n")}
const routes: readonly AGNOSTIC_ROUTE[] = ROUTE;
export default routes;
`, [transformer((node) => ts.isIdentifier(node) && node.text === "ROUTE" ? ts.factory.createArrayLiteralExpression(routes, true) : node), transformer((node) => ts.isIdentifier(node) && node.text === "AGNOSTIC_ROUTE" ? agnosticRouteId : node)]);
routeDeclaration = [...this.#manager.imports.toCode(), ...routeDeclaration];
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 mod - The name of the route module imported as a namespace.
* @param children - The list of child route call expressions.
*/
#createRouteData(path, mod, extension, children) {
const { named } = this.#manager.imports;
let extendModuleId;
let modNode = "";
if (extension) {
extendModuleId = named.getIdentifier("@vaadin/hilla-file-router/runtime.js", "extendModule") ?? named.add("@vaadin/hilla-file-router/runtime.js", "extendModule");
modNode = `, EXTEND_MODULE(MOD, ${JSON.stringify(extension)})`;
} else if (mod) {
modNode = `, MOD`;
}
const createRouteId = named.getIdentifier("@vaadin/hilla-file-router/runtime.js", "createRoute") ?? named.add("@vaadin/hilla-file-router/runtime.js", "createRoute");
return template(`const route = CREATE_ROUTE("${path}", ${modNode}${children ? `, CHILDREN` : ""})`, ([statement]) => statement.declarationList.declarations[0].initializer, [
transformer((node) => ts.isIdentifier(node) && node.text === "CHILDREN" ? ts.factory.createArrayLiteralExpression(children, true) : node),
transformer((node) => ts.isIdentifier(node) && node.text === "MOD" ? mod ?? ts.factory.createNull() : node),
transformer((node) => ts.isIdentifier(node) && node.text === "EXTEND_MODULE" ? extendModuleId : node),
transformer((node) => ts.isIdentifier(node) && node.text === "CREATE_ROUTE" ? createRouteId : node)
]);
}
}
export default function createRoutesFromMeta(views, urls) {
return new RouteFromMetaProcessor(views, urls).process();
}
//# sourceMappingURL=./createRoutesFromMeta.js.map