@vaadin/hilla-file-router
Version:
Hilla file-based router
82 lines • 3.04 kB
JavaScript
import { readFile } from "node:fs/promises";
import { Script } from "node:vm";
import ts from "typescript";
import { convertComponentNameToTitle } from "../shared/convertComponentNameToTitle.js";
import { transformTree } from "../shared/transformTree.js";
import { convertFSRouteSegmentToURLPatternFormat, extractParameterFromRouteSegment } from "./utils.js";
/**
* Walks the TypeScript AST using the deep-first search algorithm.
*
* @param node - The node to walk.
*/
function* walkAST(node) {
yield node;
for (const child of node.getChildren()) {
yield* walkAST(child);
}
}
/**
* Creates a map of all leaf routes to their configuration. This file is used by the server to provide server-side
* routes along with managing the client-side routes.
*
* @param views - The route metadata tree.
* @returns A view configuration tree.
*/
export default async function createViewConfigJson(views) {
return await transformTree(views, null, async (routes, next) => await Promise.all(routes.map(async ({ path, file, layout, children, flowLayout }) => {
const newChildren = children ? await next(children) : undefined;
if (!file && !layout) {
return {
route: convertFSRouteSegmentToURLPatternFormat(path),
params: extractParameterFromRouteSegment(path),
children: newChildren
};
}
const sourceFile = ts.createSourceFile("f.ts", await readFile(file ?? layout, "utf8"), ts.ScriptTarget.ESNext, true);
let config;
let waitingForIdentifier = false;
let componentName;
for (const node of walkAST(sourceFile)) {
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === "config") {
if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {
const code = node.initializer.getText(sourceFile);
const script = new Script(`
function key(x) {
return x[0];
}
(${code})
`);
config = script.runInThisContext();
if (config.flowLayout === undefined) {
const copy = JSON.parse(JSON.stringify(config));
copy.flowLayout = flowLayout ?? false;
config = copy;
}
}
} else if (node.getText(sourceFile).startsWith("export default")) {
waitingForIdentifier = true;
} else if (waitingForIdentifier && ts.isIdentifier(node)) {
componentName = node.text;
waitingForIdentifier = false;
}
}
config ??= { flowLayout: flowLayout ?? false };
let title;
if (config.title) {
({title} = config);
} else {
if (!componentName) {
throw new Error(`The file "${String(file ?? layout)}" must contain a default export of a component whose name will be used as title by default`);
}
title = convertComponentNameToTitle(componentName);
}
return {
route: convertFSRouteSegmentToURLPatternFormat(path),
...config,
params: extractParameterFromRouteSegment(config.route ?? path),
title,
children: newChildren ?? (layout ? [] : undefined)
};
})));
}
//# sourceMappingURL=./createViewConfigJson.js.map