@vaadin/hilla-file-router
Version:
Hilla file-based router
103 lines • 3.88 kB
JavaScript
import { basename } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { generateRuntimeFiles } from "./vite-plugin/generateRuntimeFiles.js";
const INJECTION = "if (Object.keys(nextExports).length === 2 && 'default' in nextExports && 'config' in nextExports) {nextExports = { ...nextExports, config: currentExports.config };}";
/**
* A Vite plugin that generates a router from the files in the specific directory.
*
* @param options - The plugin options.
* @returns A Vite plugin.
*/
export default function vitePluginFileSystemRouter({ viewsDir = "frontend/views/", generatedDir = "frontend/generated/", extensions = [".tsx", ".jsx"], isDevMode = false, debug = false } = {}) {
let _viewsDir;
let _outDir;
let _logger;
let runtimeUrls;
let _generateRuntimeFiles;
let _viewsDirPosix;
return {
name: "vite-plugin-file-router",
enforce: "pre",
configResolved({ logger, root, build: { outDir } }) {
const _root = pathToFileURL(root);
const _generatedDir = new URL(generatedDir, _root);
_viewsDir = new URL(viewsDir, _root);
_viewsDirPosix = fileURLToPath(_viewsDir).replaceAll("\\", "/");
_outDir = pathToFileURL(outDir);
_logger = logger;
if (debug) {
_logger.info(`The directory of route files: ${String(_viewsDir)}`);
_logger.info(`The directory of generated files: ${String(_generatedDir)}`);
_logger.info(`The output directory: ${String(_outDir)}`);
}
runtimeUrls = {
json: new URL("file-routes.json", isDevMode ? _generatedDir : _outDir),
code: new URL("file-routes.ts", _generatedDir),
layouts: new URL("layouts.json", _generatedDir)
};
_generateRuntimeFiles = async () => {
try {
return await generateRuntimeFiles(_viewsDir, runtimeUrls, extensions, _logger, debug);
} catch (e) {
_logger.error(String(e));
return true;
}
};
},
async buildStart() {
await _generateRuntimeFiles();
},
async hotUpdate({ file, modules }) {
const fileUrlString = String(pathToFileURL(file));
if (fileUrlString === String(runtimeUrls.json)) {
this.environment.hot.send({
type: "custom",
event: "fs-route-update"
});
return [];
}
if (fileUrlString === String(runtimeUrls.code)) {
return [];
}
if (!file.startsWith(_viewsDirPosix)) {
if (fileUrlString !== String(runtimeUrls.layouts)) {
return;
}
}
if (await _generateRuntimeFiles()) {
const mg = this.environment.moduleGraph;
const fileRoutesModules = mg.getModulesByFile(fileURLToPath(runtimeUrls.code).replaceAll("\\", "/"));
await Promise.all(Array.from(fileRoutesModules, async (fileRouteModule) => {
mg.invalidateModule(fileRouteModule);
await this.environment.fetchModule(fileRouteModule.id, undefined, { cached: false });
}));
const neverImported = modules.every((module) => module.importers.size === 0);
if (neverImported) {
return [...fileRoutesModules];
}
return [...fileRoutesModules, ...modules];
}
return undefined;
},
transform(code, id) {
let modifiedCode = code;
if (id.startsWith(_viewsDirPosix) && !basename(id).startsWith("_")) {
if (isDevMode) {
const injectionPattern = /import\.meta\.hot\.accept[\s\S]+if\s\(!nextExports\)\s+return;/gu;
if (injectionPattern.test(modifiedCode)) {
modifiedCode = `${modifiedCode.substring(0, injectionPattern.lastIndex)}${INJECTION}${modifiedCode.substring(injectionPattern.lastIndex)}`;
}
} else {
const functionNames = /export\s+default\s+(?:function\s+)?(\w+)/u.exec(modifiedCode);
if (functionNames?.length) {
const [, functionName] = functionNames;
modifiedCode += `Object.defineProperty(${functionName}, 'name', { value: '${functionName}' });\n`;
}
}
return { code: modifiedCode };
}
return undefined;
}
};
}
//# sourceMappingURL=./vite-plugin.js.map