UNPKG

@bgscore/react-router

Version:

Automatic React Router generator for Vite with TypeScript support

2 lines (1 loc) 9.78 kB
import t from"path";import e from"fs";import n from"fs/promises";function r(t,e,{baseUrl:n,authGuardDir:r,notFoundDir:o}){function a(t,o,s=!1){const c=" ".repeat(o),i=[];if(i.push(`${c}{`),t.isIndex?i.push(`${c} index: true,`):i.push(`${c} path: "${t.path}",`),t.element){let e=`<${t.element} />`;const n=t.imports.some((t=>"layout"===t.type));if(r&&!n&&(e=`<AuthGuard>${e}</AuthGuard>`),i.push(`${c} element: ${e},`),t.hasErrorBoundary&&n){const e=t.imports.find((t=>"layout"===t.type));e&&(i.push(`${c} errorElement: (async () => {`),i.push(`${c} const mod = await import("${e.path}");`),i.push(`${c} const rawErrorElement = mod.metadata?.errorElement;`),i.push(`${c} if (React.isValidElement(rawErrorElement)) {`),i.push(`${c} return rawErrorElement;`),i.push(`${c} } else if (typeof rawErrorElement === 'function') {`),i.push(`${c} return React.createElement(React.Suspense, { fallback: React.createElement('div', null, '') }, React.createElement(rawErrorElement));`),i.push(`${c} } else {`),i.push(`${c} return null;`),i.push(`${c} }`),i.push(`${c} })(),`))}}const u=t.imports.find((t=>"component"===t.type));if(u&&e.find((t=>t.path===u.path))){const e=function(t,e){return t.replace(new RegExp(`^${e}/?`),"").replace(/\[(.+?)\]/g,((t,e)=>e)).replace(/\//g,"-").toLowerCase()}(u.path,n);i.push(`${c} id: "${e}",`),i.push(`${c} loader: async (...props) => {`),i.push(`${c} const mod = await import("${u.path}");`),i.push(`${c} const { errorElement, ...metadata } = mod.metadata ?? {};`),i.push(`${c} return {`),i.push(`${c} metadata,`),i.push(`${c} data: mod.metadata?.loader ? await mod.metadata.loader(...props) : undefined`),i.push(`${c} };`),i.push(`${c} },`),t.hasErrorBoundary&&(i.push(`${c} errorElement: (async () => {`),i.push(`${c} const mod = await import("${u.path}");`),i.push(`${c} const rawErrorElement = mod.metadata?.errorElement;`),i.push(`${c} if (React.isValidElement(rawErrorElement)) {`),i.push(`${c} return rawErrorElement;`),i.push(`${c} } else if (typeof rawErrorElement === 'function') {`),i.push(`${c} return React.createElement(React.Suspense, { fallback: React.createElement('div', null, '') }, React.createElement(rawErrorElement));`),i.push(`${c} } else {`),i.push(`${c} return null;`),i.push(`${c} }`),i.push(`${c} })(),`))}return t.children.length>0&&(i.push(`${c} children: [`),t.children.forEach(((e,n)=>{const r=a(e,o+4,!1);i.push(r+(n<t.children.length-1?",":""))})),i.push(`${c} ]`)),i.push(`${c}}`),i.join("\n")}let s=t.map((t=>a(t,2,!0))).join(",\n");return o&&(s+=',\n { path: "*", element: <NotFound /> }'),`[\n${s}\n]`}function o(e,n){return t.relative(n,e).split(t.sep).map((t=>{let e=t.replace(/\.tsx$/,"")?.trim();return e=e.replace(/\s+/g,"_").replace(/\[(\w+)\]/g,((t,e)=>e.charAt(0).toUpperCase()+e.slice(1))),"index"===e?"Index":e})).filter(Boolean).join("-").split(/[\/\-_]/).map((t=>t.charAt(0).toUpperCase()+t.slice(1))).join("")}function a(t){try{return e.readFileSync(t,"utf-8").includes("export const metadata")}catch{return!1}}function s(t){try{const n=e.readFileSync(t,"utf-8"),r=/export\s+const\s+metadata\s*:\s*.*?\s*=\s*({[\s\S]*?})?/,o=n.match(r);if(o){const t=o[1].replace(/(\n|\r|\t| )/g,"");return{independent:t.includes("independent:true"),layout:!t.includes("layout:false")}}return{independent:!1,layout:!0}}catch{return{independent:!1,layout:!0}}}function c(t,e){const n={...t[0],children:[]};let r=n;for(let e=1;e<t.length;e++){const n={...t[e],children:[]};r.children=[n],r=n}return r.children=[e],n}function i(t){return t.startsWith("[")&&t.endsWith("]")?`:${t.slice(1,-1)}`:t}function u(n,r,l,m,d){let p=[];const f=e.readdirSync(n).sort(((t,e)=>t===`${m}.tsx`?-1:e===`${m}.tsx`?1:t.localeCompare(e))),$=d.slice(),h=f.find((t=>t===`${m}.tsx`));let y=d.slice();if(h){const a=t.join(n,h),s=o(a,r),c=`${l}/${t.relative(r,a).replace(/\.tsx$/,"").replace(/\\/g,"/")}`,u=n===r?"/":i(t.basename(n));let m=!1,d=!1,p=!1;if(e.statSync(a).isFile()){const t=e.readFileSync(a,"utf8").match(/export\s+const\s+metadata\s*(?:\:\s*[\w\<\>\[\]]+)?\s*=\s*\{([\s\S]*?)\}/m);if(t){const e=[...t[1].matchAll(/([a-zA-Z0-9_]+)\s*:/g)].map((t=>t[1]));p=e.includes("metadata"),m=e.includes("errorElement"),d=e.includes("loader")}}const f={path:u,element:s,children:[],isIndex:!1,imports:[{name:s,path:c,type:"layout"}],hasErrorBoundary:m,hasMetadata:p,hasLoader:d};y=y.concat(f)}else if(n!==r){const e={path:i(t.basename(n)),children:[],imports:[],isIndex:!1};y=y.concat(e)}for(const d of f){const f=t.join(n,d),h=e.statSync(f);let E=!1,g=!1,w=!1;if(h.isFile()){const t=e.readFileSync(f,"utf8").match(/export\s+const\s+metadata\s*(?:\:\s*[\w\<\>\[\]]+)?\s*=\s*\{([\s\S]*?)\}/m);if(t){const e=[...t[1].matchAll(/([a-zA-Z0-9_]+)\s*:/g)].map((t=>t[1]));w=e.includes("metadata"),E=e.includes("errorElement"),g=e.includes("loader")}}if(h.isFile()&&".tsx"===t.extname(d)&&d!==`${m}.tsx`&&"root.tsx"!==d){const e=t.basename(d,".tsx"),u="index"===e,m=e.startsWith("[")&&e.endsWith("]"),h=u?"":m?`:${e.slice(1,-1)}`:e,x=s(f),{independent:R=!1,layout:D=!0}=x,A=o(f,r),I={path:h,isIndex:u,element:A,children:[],imports:[{name:A,path:`${l}/${t.relative(r,f).replace(/\.tsx$/,"").replace(/\\/g,"/")}`,type:"component",hasMetadata:a(f)}],isIndependent:R,metadata:x,hasErrorBoundary:E,hasMetadata:w,hasLoader:g},b=!1===D&&y.length>$.length?$.concat({path:n===r?"/":i(t.basename(n)),children:[],imports:[],isIndex:!1}):y;if(R){const t=b.length?b[b.length-1]:null,e={path:b.map((t=>t.path)).filter((t=>"/"!==t)).join("/").replace(/\/+/g,"/")||(u?"":h),element:t?t.element:void 0,children:[I],imports:t?t.imports:[],isIndex:!1};p.push(e)}else{const t=b.length>0?c(b,I):I;p.push(t)}}else if(h.isDirectory()){const t=u(f,r,l,m,y);p=p.concat(t)}}return p}function l(t,e){return t.map((t=>{const n={...t,imports:t.imports?[...t.imports]:[],children:t.children?t.children.map((t=>({...t}))):[]};if(n.children&&n.children.length>0&&(n.children=l(n.children,e),1===n.children.length&&n.children[0].isIndex,!n.children.some((t=>"*"===t.path)))){n.imports.some((t=>"NotFound"===t.name))||n.imports.push({name:"NotFound",path:e,type:"component"});const t={path:"*",element:"NotFound",children:[],imports:[],isIndex:!1};n.children=[...n.children,t]}return n}))}function m(t,e,n,r,o){const a=u(e,t,n,r,[]);let s=[];return o&&(s=l(a,o)),o?s:a}function d(t){const e=new Map;for(const n of t){n.children=d(n.children);const t=n.path+"|"+(n.element||"");if(e.has(t)){const r=e.get(t);r.children=d(r.children.concat(n.children)),r.isIndex=r.isIndex||n.isIndex}else e.set(t,{...n})}return Array.from(e.values())}async function p(o){const{sourceDir:a="src/pages",outputDir:s="src/shared/routes",outputName:c="bgs-routes",baseUrl:i="pages",layoutName:u="_layout",authGuardDir:l,notFoundDir:p,errorBoundaryDir:f,minify:$=!0,logging:h=!0}=o||{};try{const o=t.resolve(process.cwd(),a),y=t.resolve(process.cwd(),s,`${c}.tsx`),E=e.readdirSync(o).some((t=>"root.tsx"===t)),g=m(o,o,i,u,p),{imports:w,lazyImports:x,metadataImports:R}=function(t){const e=new Map,n=new Map,r=new Map;return t.forEach((function t(o){o.imports.forEach((t=>{if("layout"===t.type)e.has(t.name)||e.set(t.name,{name:t.name,path:t.path});else if(n.has(t.name)||n.set(t.name,{name:t.name,path:t.path}),t.hasMetadata){const e=`metadata${t.name}`;r.set(t.path,{variableName:e,path:t.path})}})),o.children.forEach(t)})),{imports:Array.from(e.values()),lazyImports:Array.from(n.values()),metadataImports:Array.from(r.values())}}(g),D=[...w.map((t=>`import ${t.name} from "${t.path}";`)),...x.map((t=>`const ${t.name} = lazy(() => import("${t.path}"));`)),...l?[`import AuthGuard from "${l}";`]:[],...E?[`import Root from "${i}/root";`]:[],...f?[`import ErrorBoundary from "${f}";`]:[]].join("\n");let A=r(d(g),R,{sourceDir:a,outputDir:s,outputName:c,baseUrl:i,layoutName:u,minify:$,logging:h,authGuardDir:l,notFoundDir:p});E&&(A=`[{\n element: <Root />,\n ${f?"errorElement: <ErrorBoundary />,":""}\n children: ${A}\n }]`);let I=`\n// @ts-nocheck\nimport { createBrowserRouter } from "react-router-dom";\nimport React, { lazy } from "react";\n\n${D}\n\nexport const routes = ${A};\n\nconst router = createBrowserRouter(routes);\n\nexport default router;\n `.trim();$&&(I=`// @ts-nocheck \n${I.replace(/\/\/.*$/gm,"").replace(/\/\*[\s\S]*?\*\//g,"").replace(/[\n\r\t]/g,"").replace(/\s+/g," ")}`);let b=!0;try{await n.readFile(y,"utf-8")===I&&(b=!1)}catch{}b&&(await n.mkdir(t.dirname(y),{recursive:!0}),await n.writeFile(y,I,"utf-8"))}catch(t){}}const f=t=>{const e=t?.sourceDir||"src/pages",n=function(){let n;return function(...r){clearTimeout(n),n=setTimeout((()=>{(n=>{n?.replace(/\\/g,"/")?.includes(e)&&p(t)})(...r)}),500)}}();return{name:"bgs-react-router",enforce:"pre",async configResolved(){await p(t)},async buildStart(){await p(t)},transform(t,n){if(n?.replace(/\\/g,"/")?.includes(e)){const r=n.endsWith(".tsx")||n.endsWith(".jsx")?o(n,e):"";return`\n (function() {\n const originalWarn = console.warn;\n console.warn = function(msg, ...args) {\n if (typeof msg === "string" && msg.includes("No \`HydrateFallback\` element")) {\n return;\n }\n originalWarn.apply(console, [msg, ...args]);\n };\n })();\n \n ${t}\n ${r?`window.__METADATA${r} = typeof metadata !== "undefined" ? metadata : {};`:""}\n `}},configureServer(t){n(e),t.watcher.on("add",n).on("change",n).on("unlink",n).on("addDir",n).on("unlinkDir",n)}}};export{f as default};