nuxt-domain-driven
Version:
Hyper opinionated Nuxt module for domain driven architecture
233 lines (227 loc) • 7.67 kB
JavaScript
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 };