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
JavaScript
// 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
};