UNPKG

nuxt-domain-driven

Version:

Hyper opinionated Nuxt module for domain driven architecture

233 lines (227 loc) 7.67 kB
import { readdirSync, existsSync, lstatSync } from 'node:fs'; import { useNuxt, resolveFiles, addServerHandler, defineNuxtModule, addComponentsDir, addImportsDir } from '@nuxt/kit'; import { relative, extname, join } from 'pathe'; import { encodePath, withLeadingSlash, joinURL } from 'ufo'; import escapeRE from 'escape-string-regexp'; import { readdir } from 'node:fs/promises'; const PARAM_CHAR_RE = /[\w.]/; function parseSegment(segment) { let state = 0 /* initial */; let i = 0; let buffer = ""; const tokens = []; function consumeBuffer() { if (!buffer) { return; } if (state === 0 /* initial */) { throw new Error("wrong state"); } tokens.push({ type: state === 1 /* static */ ? 0 /* static */ : state === 2 /* dynamic */ ? 1 /* dynamic */ : state === 3 /* optional */ ? 2 /* optional */ : 3 /* catchall */, value: buffer }); buffer = ""; } while (i < segment.length) { const c = segment[i]; switch (state) { case 0 /* initial */: buffer = ""; if (c === "[") { state = 2 /* dynamic */; } else { i--; state = 1 /* static */; } break; case 1 /* static */: if (c === "[") { consumeBuffer(); state = 2 /* dynamic */; } else { buffer += c; } break; case 4 /* catchall */: case 2 /* dynamic */: case 3 /* optional */: if (buffer === "...") { buffer = ""; state = 4 /* catchall */; } if (c === "[" && state === 2 /* dynamic */) { state = 3 /* optional */; } if (c === "]" && (state !== 3 /* optional */ || segment[i - 1] === "]")) { if (!buffer) { throw new Error("Empty param"); } else { consumeBuffer(); } state = 0 /* initial */; } else if (PARAM_CHAR_RE.test(c)) { buffer += c; } else ; break; } i++; } if (state === 2 /* dynamic */) { throw new Error(`Unfinished param "${buffer}"`); } consumeBuffer(); return tokens; } function getRoutePath(tokens) { return tokens.reduce((path, token) => { return path + (token.type === 2 /* optional */ ? `:${token.value}?` : token.type === 1 /* dynamic */ ? `:${token.value}()` : token.type === 3 /* catchall */ ? `:${token.value}(.*)*` : encodePath(token.value).replace(/:/g, "\\:")); }, "/"); } async function existDir(path) { try { await readdir(path); return true; } catch (e) { return false; } } async function generatePages(dir, domain, options) { const nuxt = useNuxt(); const files = await resolveFiles(dir, `**/*{${nuxt.options.extensions.join(",")}}`); const pages = []; const scannedFiles = []; scannedFiles.push(...files.map((file) => ({ relativePath: relative(dir, file), absolutePath: file, domain }))); scannedFiles.sort((a, b) => a.relativePath.localeCompare(b.relativePath, "en-US")); for (const file of scannedFiles) { const segments = file.relativePath.replace(new RegExp(`${escapeRE(extname(file.relativePath))}$`), "").split("/"); const route = { name: domain, path: options.domains?.domainPathAlias?.[domain] ?? domain ? join("/", options.domains?.domainPathAlias?.[domain] ?? domain) : "", file: file.absolutePath, children: [] }; let parent = pages; for (let i = 0; i < segments.length; i++) { const segment = segments[i]; const tokens = parseSegment(segment); const segmentName = tokens.map(({ value }) => value).join(""); route.name += (route.name && "/") + segmentName; const path = withLeadingSlash(joinURL(route.path, getRoutePath(tokens).replace(/\/index$/, "/"))); const child = parent.find((parentRoute) => parentRoute.name === route.name && parentRoute.path === path); if (child && child.children) { parent = child.children; route.path = ""; } else if (segmentName === "index" && !route.path) { route.path += "/"; } else if (segmentName !== "index") { route.path += getRoutePath(tokens); } } if (options.onPageGenerated) { await options.onPageGenerated(route); } parent.push(route); } return prepareRoutes(pages); } function prepareRoutes(routes, parent, names = /* @__PURE__ */ new Set()) { for (const route of routes) { if (route.name) { route.name = route.name.replace(/\/index$/, "").replace(/\//g, "-"); } if (parent && route.path[0] === "/") { route.path = route.path.slice(1); } if (route.children?.length) { route.children = prepareRoutes(route.children, route, names); } if (route.children?.find((childRoute) => childRoute.path === "")) { delete route.name; } if (route.name) { names.add(route.name); } } return routes; } async function addServerDirWithDomain(dir, domain) { if (await existDir(join(dir, "api"))) { await addServerHandlers(join(dir, "api"), domain, 1 /* Api */); } if (await existDir(join(dir, "routes"))) { await addServerHandlers(join(dir, "routes"), domain, 0 /* Route */); } } async function addServerHandlers(dir, prefix, type) { const files = await resolveFiles(dir, `**/*{.ts,.js}`); for (const file of files) { addServerHandler({ handler: file, route: getRouteFromFile(relative(dir, file), prefix, type) }); } } function getRouteFromFile(file, domain, handlerType) { const segments = file.replace(new RegExp(`${escapeRE(extname(file))}$`), "").split("/"); let routePath = handlerType === 1 /* Api */ ? join("/api", domain) : join("domain"); for (let i = 0; i < segments.length; i++) { const segment = segments[i]; const tokens = parseSegment(segment); routePath += getRoutePath(tokens); } return routePath; } const module = defineNuxtModule({ meta: { name: "nuxt-domain-driven", configKey: "domainDrivenConfig", compatibility: { bridge: false, nuxt: ">=3" } }, // Default configuration options of the Nuxt module defaults: {}, async setup(options, nuxt) { const { directory = "src" } = options; const { rootDir } = nuxt.options; const directoryDir = join(rootDir, directory); const contents = await readdirSync(directoryDir); const registeredPages = []; for (const content of contents) { if (directoryExist(join(directoryDir, content))) { if (directoryExist(join(directoryDir, content, "components"))) { addComponentsDir({ path: join(directoryDir, content, "components"), prefix: content, watch: true }); } const composableDir = join(directoryDir, content, "composables"); if (directoryExist(composableDir)) { addImportsDir(composableDir); } const utilsDir = join(directoryDir, content, "utils"); if (directoryExist(utilsDir)) { addImportsDir(utilsDir); } const pagesDir = join(directoryDir, content, "pages"); if (directoryExist(pagesDir)) { registeredPages.push(...await generatePages(pagesDir, content, options)); } const serverDir = join(directoryDir, content, "server"); if (directoryExist(serverDir)) { await addServerDirWithDomain(serverDir, content); } } } nuxt.hook("pages:extend", (pages) => { pages.push(...registeredPages); }); } }); function directoryExist(directory) { return existsSync(directory) && lstatSync(directory).isDirectory(); } export { module as default };