Skip to content

Commit

Permalink
feat: add support for error and loading screens
Browse files Browse the repository at this point in the history
  • Loading branch information
mychidarko committed Nov 23, 2023
1 parent 0afe0b9 commit 6e3889b
Show file tree
Hide file tree
Showing 18 changed files with 222 additions and 60 deletions.
3 changes: 3 additions & 0 deletions apps/example/.gitignore
Expand Up @@ -22,3 +22,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?

# Hana specific
.hana
10 changes: 10 additions & 0 deletions apps/example/pages/_404.tsx
@@ -0,0 +1,10 @@
const ErrorPage = () => {
return (
<div>
<h1>404 - Page Not Found</h1>
<p>This is my custom 404 page</p>
</div>
);
}

export default ErrorPage;
6 changes: 6 additions & 0 deletions apps/example/pages/_app.tsx
Expand Up @@ -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';

Expand All @@ -25,6 +28,9 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
{createRouter({
root: import.meta.url,
loadingPages,
errorPages,
_404,
routes,
})}
</React.StrictMode>
Expand Down
9 changes: 9 additions & 0 deletions apps/example/pages/dashboard/_loading.tsx
@@ -0,0 +1,9 @@
const Loading = () => {
return (
<div>
Loading dashboard component...
</div>
);
};

export default Loading;
5 changes: 5 additions & 0 deletions apps/example/pages/dashboard/dashboard/[slug].tsx
@@ -0,0 +1,5 @@
const Dashboard = () => {
return <div>Dashboard</div>;
};

export default Dashboard;
7 changes: 7 additions & 0 deletions apps/example/pages/dashboard/dashboard/_error.tsx
@@ -0,0 +1,7 @@
const Error = () => {
return (
<div>Error screen for dashboard dashboard routes</div>
);
};

export default Error;
9 changes: 9 additions & 0 deletions apps/example/pages/dashboard/dashboard/_loading.tsx
@@ -0,0 +1,9 @@
const Loading = () => {
return (
<div>
Loading dashboard dashboard component...
</div>
);
};

export default Loading;
6 changes: 6 additions & 0 deletions apps/example/pages/dashboard/dashboard/index.tsx
@@ -0,0 +1,6 @@
const Dashboard = () => {
console.log(__dirname);
return <div>Dashboard</div>;
}

export default Dashboard;
3 changes: 3 additions & 0 deletions apps/example/pages/dashboard/dashboard/people.tsx
@@ -0,0 +1,3 @@
export default function People() {
return <div>People</div>;
}
3 changes: 3 additions & 0 deletions apps/example/pages/dashboard/dashboard/van.tsx
@@ -0,0 +1,3 @@
export default function Van() {
return <div>Van</div>;
}
1 change: 1 addition & 0 deletions apps/example/pages/dashboard/index.tsx
@@ -1,4 +1,5 @@
const Dashboard = () => {
console.log(__dirname);
return <div>Dashboard</div>;
}

Expand Down
47 changes: 0 additions & 47 deletions apps/example/pages/index.css
Expand Up @@ -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;
}
}
4 changes: 4 additions & 0 deletions apps/example/pages/index.tsx
@@ -0,0 +1,4 @@
export default function Index() {
__dirname;
return <div>Index</div>;
}
1 change: 1 addition & 0 deletions packages/router/package.json
Expand Up @@ -29,6 +29,7 @@
},
"dependencies": {
"@types/node": "^18.11.17",
"chalk": "^5.3.0",
"react-router-dom": "^6.19.0",
"vite": "^5.0.0"
}
Expand Down
3 changes: 3 additions & 0 deletions packages/router/src/@types/index.ts
Expand Up @@ -5,5 +5,8 @@ export interface HanaOptions {

export interface RouterOptions {
root: string;
_404: string[];
errorPages: string[];
loadingPages: string[];
routes: any[];
}
96 changes: 90 additions & 6 deletions packages/router/src/router.tsx
Expand Up @@ -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'),
});
}

Expand Down
61 changes: 54 additions & 7 deletions packages/router/src/vite-plugin.ts
Expand Up @@ -10,36 +10,68 @@ 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,
});

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
Expand All @@ -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 {
Expand Down

0 comments on commit 6e3889b

Please sign in to comment.