129 lines
3.9 KiB
TypeScript
129 lines
3.9 KiB
TypeScript
import { defineNuxtModule, useLogger } from "@nuxt/kit";
|
|
import { readdir, readFile, writeFile } from "fs/promises";
|
|
import { join, relative, parse } from "path";
|
|
import { existsSync, watch } from "fs";
|
|
|
|
type AssetsTree = {
|
|
[key: string]: string | AssetsTree;
|
|
};
|
|
|
|
const IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".webp", ".gif"]);
|
|
|
|
export default defineNuxtModule({
|
|
meta: {
|
|
name: "image-assets",
|
|
configKey: "imageAssets",
|
|
},
|
|
defaults: {},
|
|
async setup(_, nuxt) {
|
|
const logger = useLogger("image-assets");
|
|
const publicImagesDir = join(nuxt.options.rootDir, "public/images");
|
|
const templateFile = join(
|
|
nuxt.options.rootDir,
|
|
"app/composables/useAssets.ts.in",
|
|
);
|
|
const outputFile = join(
|
|
nuxt.options.rootDir,
|
|
"app/composables/useAssets.ts",
|
|
);
|
|
|
|
const isImageFile = (filename: string): boolean => {
|
|
const ext = parse(filename).ext.toLowerCase();
|
|
return IMAGE_EXTENSIONS.has(ext);
|
|
};
|
|
|
|
const scanDirectory = async (dir: string): Promise<string[]> => {
|
|
const images: string[] = [];
|
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
images.push(...(await scanDirectory(fullPath)));
|
|
} else if (isImageFile(entry.name)) {
|
|
images.push(fullPath);
|
|
}
|
|
}
|
|
|
|
return images;
|
|
};
|
|
|
|
const toCamelCase = (str: string): string => {
|
|
return str.replace(/[-_](.)/g, (_, c) => c.toUpperCase());
|
|
};
|
|
|
|
const buildAssetsTree = (images: string[], baseDir: string): AssetsTree => {
|
|
const tree: AssetsTree = {};
|
|
|
|
for (const imagePath of images) {
|
|
const relativePath = relative(baseDir, imagePath);
|
|
const parts = relativePath.split("/");
|
|
const filename = parse(parts[parts.length - 1]!).name;
|
|
|
|
let current = tree;
|
|
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
const key = toCamelCase(parts[i]!);
|
|
current[key] ??= {};
|
|
current = current[key] as AssetsTree;
|
|
}
|
|
|
|
current[toCamelCase(filename)] = `/images/${relativePath}`;
|
|
}
|
|
|
|
return tree;
|
|
};
|
|
|
|
const generateAssetsObject = (tree: AssetsTree, indent = 0): string => {
|
|
const spaces = " ".repeat(indent);
|
|
const entries = Object.entries(tree);
|
|
if (!entries.length) return "{}";
|
|
|
|
const lines = entries.map(([key, value]) =>
|
|
typeof value === "string"
|
|
? `${spaces} ${key}: createImage("${value}"),`
|
|
: `${spaces} ${key}: ${generateAssetsObject(value, indent + 1)},`,
|
|
);
|
|
|
|
return `{\n${lines.join("\n")}\n${spaces}}`;
|
|
};
|
|
|
|
const generateAssetsFile = async () => {
|
|
try {
|
|
if (!existsSync(publicImagesDir)) {
|
|
logger.warn("No public/images directory found");
|
|
return;
|
|
}
|
|
|
|
const images = await scanDirectory(publicImagesDir);
|
|
const assetsTree = buildAssetsTree(images, publicImagesDir);
|
|
const assetsObject = generateAssetsObject(assetsTree);
|
|
|
|
const template = await readFile(templateFile, "utf-8");
|
|
const fileContent = template
|
|
.replace("{{TOTAL}}", images.length.toString())
|
|
.replace("{{ASSETS}}", assetsObject);
|
|
|
|
await writeFile(outputFile, fileContent, "utf-8");
|
|
logger.success(`Generated useAssets.ts with ${images.length} images`);
|
|
} catch (error) {
|
|
logger.error("Error generating assets file:", error);
|
|
}
|
|
};
|
|
|
|
nuxt.hook("build:before", async () => {
|
|
await generateAssetsFile();
|
|
});
|
|
|
|
if (nuxt.options.dev) {
|
|
nuxt.hook("ready", () => {
|
|
watch(publicImagesDir, { recursive: true }, async (_, filePath) => {
|
|
if (filePath && isImageFile(filePath)) {
|
|
logger.info(`Detected change: ${filePath}`);
|
|
await generateAssetsFile();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
},
|
|
});
|