From 6e3889b64038e321505d93b0ae395a4a71bceace Mon Sep 17 00:00:00 2001 From: mychidarko Date: Thu, 23 Nov 2023 14:15:30 +0000 Subject: [PATCH] feat: add support for error and loading screens --- apps/example/.gitignore | 3 + apps/example/pages/_404.tsx | 10 ++ apps/example/pages/_app.tsx | 6 ++ apps/example/pages/dashboard/_loading.tsx | 9 ++ .../pages/dashboard/dashboard/[slug].tsx | 5 + .../pages/dashboard/dashboard/_error.tsx | 7 ++ .../pages/dashboard/dashboard/_loading.tsx | 9 ++ .../pages/dashboard/dashboard/index.tsx | 6 ++ .../pages/dashboard/dashboard/people.tsx | 3 + .../example/pages/dashboard/dashboard/van.tsx | 3 + apps/example/pages/dashboard/index.tsx | 1 + apps/example/pages/index.css | 47 --------- apps/example/pages/index.tsx | 4 + packages/router/package.json | 1 + packages/router/src/@types/index.ts | 3 + packages/router/src/router.tsx | 96 +++++++++++++++++-- packages/router/src/vite-plugin.ts | 61 ++++++++++-- pnpm-lock.yaml | 8 ++ 18 files changed, 222 insertions(+), 60 deletions(-) create mode 100644 apps/example/pages/_404.tsx create mode 100644 apps/example/pages/dashboard/_loading.tsx create mode 100644 apps/example/pages/dashboard/dashboard/[slug].tsx create mode 100644 apps/example/pages/dashboard/dashboard/_error.tsx create mode 100644 apps/example/pages/dashboard/dashboard/_loading.tsx create mode 100644 apps/example/pages/dashboard/dashboard/index.tsx create mode 100644 apps/example/pages/dashboard/dashboard/people.tsx create mode 100644 apps/example/pages/dashboard/dashboard/van.tsx create mode 100644 apps/example/pages/index.tsx diff --git a/apps/example/.gitignore b/apps/example/.gitignore index a547bf3..7b20100 100644 --- a/apps/example/.gitignore +++ b/apps/example/.gitignore @@ -22,3 +22,6 @@ dist-ssr *.njsproj *.sln *.sw? + +# Hana specific +.hana diff --git a/apps/example/pages/_404.tsx b/apps/example/pages/_404.tsx new file mode 100644 index 0000000..347ece3 --- /dev/null +++ b/apps/example/pages/_404.tsx @@ -0,0 +1,10 @@ +const ErrorPage = () => { + return ( +
+

404 - Page Not Found

+

This is my custom 404 page

+
+ ); +} + +export default ErrorPage; diff --git a/apps/example/pages/_app.tsx b/apps/example/pages/_app.tsx index a9c11d8..2886d2a 100644 --- a/apps/example/pages/_app.tsx +++ b/apps/example/pages/_app.tsx @@ -3,7 +3,10 @@ import ReactDOM from 'react-dom/client'; import { createRouter } from '@hanabira/router'; import { PersistedState, createStore } from '@hanabira/store'; +import _404 from '../.hana/_404-page.json'; import routes from '../.hana/routes.json'; +import errorPages from '../.hana/error-pages.json'; +import loadingPages from '../.hana/loading-pages.json'; import './index.css'; @@ -25,6 +28,9 @@ ReactDOM.createRoot(document.getElementById('root')!).render( {createRouter({ root: import.meta.url, + loadingPages, + errorPages, + _404, routes, })} diff --git a/apps/example/pages/dashboard/_loading.tsx b/apps/example/pages/dashboard/_loading.tsx new file mode 100644 index 0000000..cc97337 --- /dev/null +++ b/apps/example/pages/dashboard/_loading.tsx @@ -0,0 +1,9 @@ +const Loading = () => { + return ( +
+ Loading dashboard component... +
+ ); +}; + +export default Loading; diff --git a/apps/example/pages/dashboard/dashboard/[slug].tsx b/apps/example/pages/dashboard/dashboard/[slug].tsx new file mode 100644 index 0000000..87ff770 --- /dev/null +++ b/apps/example/pages/dashboard/dashboard/[slug].tsx @@ -0,0 +1,5 @@ +const Dashboard = () => { + return
Dashboard
; +}; + +export default Dashboard; diff --git a/apps/example/pages/dashboard/dashboard/_error.tsx b/apps/example/pages/dashboard/dashboard/_error.tsx new file mode 100644 index 0000000..0d1f0ae --- /dev/null +++ b/apps/example/pages/dashboard/dashboard/_error.tsx @@ -0,0 +1,7 @@ +const Error = () => { + return ( +
Error screen for dashboard dashboard routes
+ ); +}; + +export default Error; diff --git a/apps/example/pages/dashboard/dashboard/_loading.tsx b/apps/example/pages/dashboard/dashboard/_loading.tsx new file mode 100644 index 0000000..6efac0c --- /dev/null +++ b/apps/example/pages/dashboard/dashboard/_loading.tsx @@ -0,0 +1,9 @@ +const Loading = () => { + return ( +
+ Loading dashboard dashboard component... +
+ ); +}; + +export default Loading; diff --git a/apps/example/pages/dashboard/dashboard/index.tsx b/apps/example/pages/dashboard/dashboard/index.tsx new file mode 100644 index 0000000..d44de3c --- /dev/null +++ b/apps/example/pages/dashboard/dashboard/index.tsx @@ -0,0 +1,6 @@ +const Dashboard = () => { + console.log(__dirname); + return
Dashboard
; +} + +export default Dashboard; diff --git a/apps/example/pages/dashboard/dashboard/people.tsx b/apps/example/pages/dashboard/dashboard/people.tsx new file mode 100644 index 0000000..a7b39bf --- /dev/null +++ b/apps/example/pages/dashboard/dashboard/people.tsx @@ -0,0 +1,3 @@ +export default function People() { + return
People
; +} diff --git a/apps/example/pages/dashboard/dashboard/van.tsx b/apps/example/pages/dashboard/dashboard/van.tsx new file mode 100644 index 0000000..f396151 --- /dev/null +++ b/apps/example/pages/dashboard/dashboard/van.tsx @@ -0,0 +1,3 @@ +export default function Van() { + return
Van
; +} diff --git a/apps/example/pages/dashboard/index.tsx b/apps/example/pages/dashboard/index.tsx index 6434275..d44de3c 100644 --- a/apps/example/pages/dashboard/index.tsx +++ b/apps/example/pages/dashboard/index.tsx @@ -1,4 +1,5 @@ const Dashboard = () => { + console.log(__dirname); return
Dashboard
; } diff --git a/apps/example/pages/index.css b/apps/example/pages/index.css index 6119ad9..7acf68a 100644 --- a/apps/example/pages/index.css +++ b/apps/example/pages/index.css @@ -13,56 +13,9 @@ -moz-osx-font-smoothing: grayscale; } -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - @media (prefers-color-scheme: light) { :root { color: #213547; background-color: #ffffff; } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } } diff --git a/apps/example/pages/index.tsx b/apps/example/pages/index.tsx new file mode 100644 index 0000000..a2607a6 --- /dev/null +++ b/apps/example/pages/index.tsx @@ -0,0 +1,4 @@ +export default function Index() { + __dirname; + return
Index
; +} diff --git a/packages/router/package.json b/packages/router/package.json index 83adbf4..4a1d3de 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -29,6 +29,7 @@ }, "dependencies": { "@types/node": "^18.11.17", + "chalk": "^5.3.0", "react-router-dom": "^6.19.0", "vite": "^5.0.0" } diff --git a/packages/router/src/@types/index.ts b/packages/router/src/@types/index.ts index 27c1558..e77f866 100644 --- a/packages/router/src/@types/index.ts +++ b/packages/router/src/@types/index.ts @@ -5,5 +5,8 @@ export interface HanaOptions { export interface RouterOptions { root: string; + _404: string[]; + errorPages: string[]; + loadingPages: string[]; routes: any[]; } diff --git a/packages/router/src/router.tsx b/packages/router/src/router.tsx index 3ae4b31..3244b10 100644 --- a/packages/router/src/router.tsx +++ b/packages/router/src/router.tsx @@ -4,19 +4,103 @@ import { RouterOptions } from './@types'; export function createRouter({ routes: appRoutes, - root + loadingPages, + errorPages, + _404, + root, }: RouterOptions) { const routes: any = []; for (const r of appRoutes) { - const Component = lazy(() => import(`${root.replace('_app.tsx', r.file)}`)); + let closestErrorPage: any = null; + let closestLoadingPage: any = null; + + let closestErrorMatchLength = 0; + let closestLoadingMatchLength = 0; + + errorPages.forEach((errorPage) => { + const routeFile = r.file.toLowerCase(); + const errorPageFile = errorPage + .replace(/\_error.(jsx|tsx|js|ts)/, '') + .toLowerCase(); + + if (routeFile.startsWith(errorPageFile)) { + const matchLength = errorPageFile.length; + + if (matchLength > closestErrorMatchLength) { + closestErrorPage = errorPage; + closestErrorMatchLength = matchLength; + } + } + }); + + loadingPages.forEach((loadingPage) => { + const routeFile = r.file.toLowerCase(); + const loadingPageFile = loadingPage + .replace(/\_loading.(jsx|tsx|js|ts)/, '') + .toLowerCase(); + + if (routeFile.startsWith(loadingPageFile)) { + const matchLength = loadingPageFile.length; + + if (matchLength > closestLoadingMatchLength) { + closestLoadingPage = loadingPage; + closestLoadingMatchLength = matchLength; + } + } + }); + + let ErrorComponent: any; + let LoadingComponent: any; + + const Component = lazy( + () => import(/* @vite-ignore */ `${root.replace('_app.tsx', r.file)}`) + ); + + if (closestErrorPage) { + ErrorComponent = lazy( + () => + import( + /* @vite-ignore */ `${root.replace('_app.tsx', closestErrorPage)}` + ) + ); + } + + if (closestLoadingPage) { + LoadingComponent = lazy( + () => + import( + /* @vite-ignore */ `${root.replace('_app.tsx', closestLoadingPage)}` + ) + ); + } routes.push({ path: r.path, - element: createElement(Suspense, { - fallback: 'Loading...', - children: createElement(Component), - }), + errorElement: closestErrorPage ? createElement(ErrorComponent) : null, + element: closestLoadingPage + ? createElement(Suspense, { + fallback: createElement(LoadingComponent), + children: createElement(Component), + }) + : createElement(Component), + }); + } + + if (_404) { + routes.push({ + path: '*', + element: createElement( + lazy( + () => + import(/* @vite-ignore */ `${root.replace('_app.tsx', _404[0])}`) + ) + ), + }); + } else { + routes.push({ + path: '*', + element: createElement('div', null, '404'), }); } diff --git a/packages/router/src/vite-plugin.ts b/packages/router/src/vite-plugin.ts index dd06654..8ee022d 100644 --- a/packages/router/src/vite-plugin.ts +++ b/packages/router/src/vite-plugin.ts @@ -10,18 +10,33 @@ export default function hana(options: HanaOptions): Plugin { file.endsWith('.jsx') || file.endsWith('.ts') || file.endsWith('.tsx')) && - !file.startsWith('_') && !file.endsWith('.d.ts') && !file.endsWith('.test.js') && !file.endsWith('.test.jsx') && !file.endsWith('.test.ts') && !file.endsWith('.test.tsx'); + const isNotHanaFile = (file: string) => + isJavascriptFile(file) && !file.includes('/_'); + + const isErrorPage = (file: string) => + isJavascriptFile(file) && file.includes('/_error.'); + + const isLoadingFile = (file: string) => + isJavascriptFile(file) && file.includes('/_loading.'); + const buildRoutes = () => { console.log('Building your routes...'); - const compileRoutes: any = (dir = 'pages') => { - const javascriptFiles = []; + const compileRoutes: (dir?: string) => { + javascriptFiles: any[]; + loadingFiles: any[]; + errorFiles: any[]; + _404Page: string; + } = (dir = 'pages') => { + const javascriptFiles: any = []; + const errorFiles: any = []; + const loadingFiles: any = []; const files = fs.readdirSync(path.resolve(options.root, dir), { withFileTypes: true, @@ -29,17 +44,34 @@ export default function hana(options: HanaOptions): Plugin { for (const file of files) { if (file.isDirectory()) { - javascriptFiles.push(...compileRoutes(`${dir}/${file.name}`)); + const data = compileRoutes(`${dir}/${file.name}`); + + javascriptFiles.push(...data.javascriptFiles); + loadingFiles.push(...data.loadingFiles); + errorFiles.push(...data.errorFiles); } else { javascriptFiles.push(`${dir}/${file.name}`.replace('pages', '')); + loadingFiles.push(`${dir}/${file.name}`.replace('pages', '')); + errorFiles.push(`${dir}/${file.name}`.replace('pages', '')); } } - return javascriptFiles.filter(isJavascriptFile); + return { + javascriptFiles: javascriptFiles.filter(isNotHanaFile), + loadingFiles: loadingFiles.filter(isLoadingFile), + errorFiles: errorFiles.filter(isErrorPage), + _404Page: javascriptFiles.find((file: string) => + file.includes('/_404.') + ), + }; }; const routes: any = []; - const appRoutes = compileRoutes(); + const appFiles = compileRoutes(); + const errorPages = appFiles.errorFiles; + const appRoutes = appFiles.javascriptFiles; + const loadingPages = appFiles.loadingFiles; + const _404Page = appFiles._404Page; appRoutes.forEach((route: string) => { const routePath = route @@ -65,13 +97,28 @@ export default function hana(options: HanaOptions): Plugin { }); }); - console.log('Routes built successfully!', routes); + console.log('Routes built successfully!'); fs.mkdirSync(path.resolve(options.root, '.hana'), { recursive: true }); fs.writeFileSync( path.resolve(options.root, '.hana/routes.json'), JSON.stringify(routes) ); + + fs.writeFileSync( + path.resolve(options.root, '.hana/error-pages.json'), + JSON.stringify(errorPages) + ); + + fs.writeFileSync( + path.resolve(options.root, '.hana/loading-pages.json'), + JSON.stringify(loadingPages) + ); + + fs.writeFileSync( + path.resolve(options.root, '.hana/_404-page.json'), + JSON.stringify([_404Page]) + ); }; return { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0fd20cd..e3570e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,6 +225,9 @@ importers: '@types/node': specifier: ^18.11.17 version: 18.18.9 + chalk: + specifier: ^5.3.0 + version: 5.3.0 react-router-dom: specifier: ^6.19.0 version: 6.19.0(react-dom@18.2.0)(react@17.0.2) @@ -2599,6 +2602,11 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: false + /chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}