UNPKG

vite-plugin-ra-pages

Version:

Vite plugin to automatically generate routes for React-Admin based on folder structure.

347 lines (338 loc) 8.75 kB
// src/main.ts import path3 from "path"; // src/scan.ts import { globSync } from "glob"; import path from "path"; var slashPath = (name) => name.replace(/\\/g, "/"); var resolveImportFile = (pathFile, root) => { let importFile = path.relative(root, path.join(...pathFile)); importFile = slashPath(importFile); if (!importFile.startsWith(".")) { importFile = `./${importFile}`; } return importFile; }; var removeSufix = (name) => name.replace(/\/(Index|Page)\.(jsx|tsx)$/, ""); var parseRoutePath = (name) => name.replace(/\/(Index|Page)\.(jsx|tsx)$/, "").replace("[", ":").replace("]", ""); var globSlash = (pattern, options) => { const files = globSync(pattern, options); return files.map((path4) => slashPath(path4)); }; var getEntries = (root) => { const options = { cwd: root, ignore: "node_modules/**" }; const resources = globSlash("**/Index.@(jsx|tsx)", options).map(removeSufix); const others = globSlash("**/@(Page|PageIndex).@(jsx|tsx)", options).filter( (path4) => !resources.some((prefix) => path4.startsWith(prefix + "/")) ); return { resources, others }; }; var getRouteResource = ({ resource, root, key }) => { const files = globSlash("**/@(Page|Index).@(jsx|tsx)", { cwd: path.join(root, resource), ignore: "node_modules/**", nodir: true }); const list = files.find((it) => it.startsWith("Index.")); const create = files.find((it) => it.startsWith("create/Page.")); const withParam = files.filter((it) => it.startsWith("[id]/")); const edit = withParam.find((it) => it.startsWith("[id]/edit/Page.")); const show = withParam.find((it) => it.startsWith("[id]/show/Page.")); const others = files.filter( (it) => it !== list && it !== create && it !== edit && it !== show ); return { key, root, resource, list, show, create, edit, others }; }; var getConfigEntries = (root) => { const { resources, others } = getEntries(root); const routeResources = resources.map((resource, ix) => { return getRouteResource({ resource, root, key: `R_${ix + 1}` }); }); const size = routeResources.length + 1; const routeFiles = others.map((file, ix) => { return { key: `R_${ix + size}`, file, root }; }); return { resources: routeResources, others: routeFiles }; }; // src/write.ts import fs from "fs"; import path2 from "path"; // src/el.ts var appendELAttr = (el, key, attrs, on) => { if (key) { Object.entries(attrs).forEach(([k, v]) => { if (typeof v === "function") { v = v(key); } if (v.startsWith("{") && v.endsWith("}")) { el.attrs[k] = v; } else if (v.startsWith("'") && v.endsWith("'")) { el.attrs[k] = v; } else if (v.startsWith('"') && v.endsWith('"')) { el.attrs[k] = v; } else { el.attrs[k] = `${key}.${v}`; } }); on(key); } }; var printImports = (root) => { const collected = []; const walk = (node) => { node.imports.filter((it) => it).forEach((imp) => collected.push(imp)); node.children.forEach((child) => walk(child)); }; walk(root); return collected.join("\n"); }; var printElement = (el, tab = " ") => { const tab1 = " ".padStart(el.tag.length + 2, " "); const attrs = Object.entries(el.attrs).map(([k, v]) => { if (k.startsWith("{") && k.endsWith("}")) { return k; } else if (v.startsWith("'") && v.endsWith("'")) { return `${k}=${v}`; } else if (v.startsWith('"') && v.endsWith('"')) { return `${k}=${v}`; } else if (v.startsWith("{") && v.endsWith("}")) { return `${k}=${v}`; } else { return `${k}={${v}}`; } }).join(` ${tab}${tab1}`); const openTag = attrs ? `<${el.tag} ${attrs}>` : `<${el.tag}>`; const children = el.children.map((child) => printElement(child, tab + " ")); return [`${tab}${openTag}`, ...children, `${tab}</${el.tag}>`].join("\n"); }; // src/write.ts var writeRAAdmin = (file, config, raConfig) => { const dirout = path2.dirname(file); const wrappers = []; const addWrap = (key) => { wrappers.push( `const ${key}Wrapper = (props) => <${key}.default {...props}/>;` ); }; const nameWrap = (key, print = false) => print ? `{<${key}Wrapper/>}` : `{${key}Wrapper}`; const root = { tag: "Admin", attrs: { "{...props}": "" }, children: [], imports: [ "import React from 'react';", "import { Admin, Resource } from 'react-admin';", "import { Route } from 'react-router-dom';" ] }; config.resources.map((rr) => { const files = [rr.list, rr.create, rr.show, rr.edit, ...rr.others].filter( (it) => it ); const keyImport = files.reduce((acc, file2, ix) => { acc[file2] = `${rr.key}_${ix + 1}`; return acc; }, {}); const el = { tag: "Resource", attrs: {}, children: [], imports: Object.entries(keyImport).map(([file2, key]) => { const importFile = resolveImportFile( [rr.root, rr.resource, file2], dirout ); if (raConfig.lazyLoad) { return `const ${key} = { default: React.lazy(() => import('${importFile}')) };`; } return `import * as ${key} from '${importFile}';`; }) }; appendELAttr( el, keyImport[rr.list], { name: `'${rr.resource}'`, icon: "Icon", options: "Options", list: nameWrap }, addWrap ); appendELAttr( el, keyImport[rr.create], { create: nameWrap }, addWrap ); appendELAttr( el, keyImport[rr.show], { show: nameWrap }, addWrap ); appendELAttr( el, keyImport[rr.edit], { edit: nameWrap }, addWrap ); rr.others.forEach((file2) => { const key = keyImport[file2]; addWrap(key); const child = { tag: "Route", attrs: { path: `'${parseRoutePath(file2)}'`, element: nameWrap(key, true) }, children: [], imports: [] }; el.children.push(child); }); return el; }).forEach((it) => { root.children.push(it); }); config.others.forEach((rf) => { const importFile = resolveImportFile([rf.root, rf.file], dirout); if (raConfig.lazyLoad) { root.imports.push( `const ${rf.key} = { default: React.lazy(() => import('${importFile}')) };` ); } else { root.imports.push(`import * as ${rf.key} from '${importFile}';`); } if (rf.file.startsWith("Page.")) { root.attrs = { dashboard: `${rf.key}.default`, ...root.attrs }; } else { const child = { tag: "Route", attrs: { path: `'${parseRoutePath(rf.file)}'`, element: `<${rf.key}.default />` }, children: [], imports: [] }; root.children.push(child); } }); const code = ` // Generate ReactAdmin ${printImports(root)} ${wrappers.join("\n")} const RAAdmin = (props)=>{ return ( ${printElement(root, " ")} ); }; export default RAAdmin; `; if (!fs.existsSync(dirout)) { fs.mkdirSync(dirout, { recursive: true }); } fs.writeFileSync(file, code); }; // src/main.ts var ensureRAConfig = ({ root = process.cwd(), pageDir = "src/pages", cacheDir = ".ra", aliasPage = "ra-pages.jsx", lazyLoad = false }) => { return { root, pageDir, cacheDir, aliasPage, lazyLoad }; }; var raPages = (raOpts = {}) => { const raConfig = ensureRAConfig(raOpts); const rootPageDir = path3.resolve(raConfig.root, raConfig.pageDir); const mainPageFile = path3.resolve( raConfig.root, raConfig.cacheDir, raConfig.aliasPage ); const shouldRegenerate = (file) => file.startsWith(rootPageDir); const regenerate = () => { const config = getConfigEntries(rootPageDir); writeRAAdmin(mainPageFile, config, raConfig); }; regenerate(); return { name: "vite-plugin-react-admin", enforce: "pre", config: () => { return { resolve: { alias: { [raConfig.aliasPage]: mainPageFile } } }; }, configureServer: (server) => { server.watcher.add(rootPageDir); server.watcher.on("add", (file) => { if (shouldRegenerate(file)) regenerate(); }); server.watcher.on("unlink", (file) => { if (shouldRegenerate(file)) regenerate(); }); server.watcher.on("change", (file) => { if (shouldRegenerate(file)) regenerate(); }); } }; }; var main_default = raPages; export { main_default as default, raPages };