Skip to content

Commit

Permalink
feature: [18] loader 기능 추가
Browse files Browse the repository at this point in the history
feature: [18] loader 기능 추가
  • Loading branch information
joseph0926 authored Oct 18, 2024
2 parents 5384cf9 + d796061 commit c05222a
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 29 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,23 @@ export default function HomeLoading() {
}
```

#### **7. Loader Support**

You can add a `loader.ts` file inside the folder to handle **loader** to that path.<br/>
For more information, see [Loader in React Router](https://reactrouter.com/en/main/route/loader)

```tsx
// src/pages/loader.ts
export default async function rootLoader() {
const res = await fetch('https://swapi.dev/api/people');

return await res.json();
}

// src/pages/layout.tsx
const data = useLoaderData();
```

---

### **📄 How to Contribute**
Expand Down
17 changes: 17 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,23 @@ export default function HomeLoading() {
}
```

#### **7. loader 지원**

폴더 내에 `loader.ts` 파일을 추가하여 해당 경로에 **loader**에 대한 처리를 수행할 수 있습니다.<br/>
자세한 내용은 [React Router의 loader](https://reactrouter.com/en/main/route/loader)를 참고해주세요

```tsx
// src/pages/loader.ts
export default async function rootLoader() {
const res = await fetch('https://swapi.dev/api/people');

return await res.json();
}

// src/pages/layout.tsx
const data = useLoaderData();
```

### **📄 기여 방법**

이 프로젝트에 기여하고 싶으시다면, 다음 절차를 따라주세요:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-router-file-routing",
"version": "0.1.4",
"version": "0.1.5",
"description": "A library to support folder-based routing of next.js to react-router-dom",
"main": "dist/index.js",
"files": [
Expand Down
6 changes: 1 addition & 5 deletions src/file-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,5 @@ export function FileRouter() {

const router = React.useMemo(() => createBrowserRouter(routes), [routes]);

return (
<React.Suspense fallback={<div>Loading,,,</div>}>
<RouterProvider router={router} />
</React.Suspense>
);
return <RouterProvider router={router} />;
}
18 changes: 11 additions & 7 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import { LoaderFunction } from 'react-router-dom';

export type RouteNode = {
/** 라우터 path */
path?: string;
/** 라우터 component */
element?: React.LazyExoticComponent<any>;
element?: React.LazyExoticComponent<React.ComponentType<any>>;
/** layout component */
layoutElement?: React.LazyExoticComponent<any>;
layoutElement?: React.LazyExoticComponent<React.ComponentType<any>>;
/** page component */
pageElement?: React.LazyExoticComponent<any>;
pageElement?: React.LazyExoticComponent<React.ComponentType<any>>;
/** error component */
errorElement?: React.LazyExoticComponent<any>;
errorElement?: React.LazyExoticComponent<React.ComponentType<any>>;
/** loading component */
loadingElement?: React.LazyExoticComponent<any>;
loadingElement?: React.LazyExoticComponent<React.ComponentType<any>>;
/** loader function */
loaderFn?: LoaderFunction;
/** children 라우트 */
children?: Record<string, RouteNode>;
};

export type NodeData = {
type: 'page' | 'layout' | 'error' | 'loading';
element: React.LazyExoticComponent<any>;
type: 'page' | 'layout' | 'error' | 'loading' | 'loader';
element: React.LazyExoticComponent<React.ComponentType<any>> | LoaderFunction;
};
51 changes: 40 additions & 11 deletions src/utils/add-tree.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LoaderFunction } from 'react-router-dom';
import { NodeData, RouteNode } from '../types';

/**
Expand All @@ -19,17 +20,28 @@ export function addToRouteTree(

/** 루트 레이아웃이면 트리의 최상위에 layoutElement를 설정 */
if (isRootLayout && nodeData.type === 'layout') {
tree.layoutElement = nodeData.element;
tree.layoutElement = nodeData.element as React.LazyExoticComponent<
React.ComponentType<any>
>;
return;
}
/** 루트 에러면 트리의 최상위에 errorElement를 설정 */
if (isRootLayout && nodeData.type === 'error') {
tree.errorElement = nodeData.element;
tree.errorElement = nodeData.element as React.LazyExoticComponent<
React.ComponentType<any>
>;
return;
}
/** 루트 로딩이면 트리의 최상위에 loadingElement를 설정 */
if (isRootLayout && nodeData.type === 'loading') {
tree.loadingElement = nodeData.element;
tree.loadingElement = nodeData.element as React.LazyExoticComponent<
React.ComponentType<any>
>;
return;
}
/** 루트 loader면 트리의 최상위에 loaderFn를 설정 */
if (isRootLayout && nodeData.type === 'loader') {
tree.loaderFn = nodeData.element as LoaderFunction;
return;
}

Expand Down Expand Up @@ -75,14 +87,31 @@ export function addToRouteTree(

/** 마지막 세그먼트인 경우 요소 할당 */
if (index === segments.length - 1) {
if (nodeData.type === 'page') {
current.pageElement = nodeData.element;
} else if (nodeData.type === 'layout') {
current.layoutElement = nodeData.element;
} else if (nodeData.type === 'error') {
current.errorElement = nodeData.element;
} else if (nodeData.type === 'loading') {
current.loadingElement = nodeData.element;
switch (nodeData.type) {
case 'page':
current.pageElement = nodeData.element as React.LazyExoticComponent<
React.ComponentType<any>
>;
break;
case 'layout':
current.layoutElement = nodeData.element as React.LazyExoticComponent<
React.ComponentType<any>
>;
break;
case 'error':
current.errorElement = nodeData.element as React.LazyExoticComponent<
React.ComponentType<any>
>;
break;
case 'loading':
current.loadingElement =
nodeData.element as React.LazyExoticComponent<
React.ComponentType<any>
>;
break;
case 'loader':
current.loaderFn = nodeData.element as LoaderFunction;
break;
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/utils/build-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ export function buildRoutesFromTree(
? React.createElement(node.loadingElement)
: undefined;

const loaderFn = node.loaderFn ? node.loaderFn : undefined;

if (node.pageElement) {
childRoutes.push({
index: true,
element: wrapWithSuspense(node.pageElement, loadingComponent),
errorElement: node.errorElement
? wrapWithSuspense(node.errorElement)
: undefined,
loader: loaderFn,
});
}

Expand Down
6 changes: 6 additions & 0 deletions src/utils/create-route-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export function createRouteObject(node: RouteNode, path?: string): RouteObject {
route.errorElement = wrapWithSuspense(node.errorElement, loadingComponent);
}

/** loader 처리 */
if (node.loaderFn) {
route.loader = node.loaderFn;
}

/** page 처리 */
if (node.pageElement) {
const pageElement = wrapWithSuspense(node.pageElement, loadingComponent);
Expand All @@ -40,6 +45,7 @@ export function createRouteObject(node: RouteNode, path?: string): RouteObject {
index: true,
element: pageElement,
errorElement: route.errorElement,
loader: route.loader,
},
];
} else {
Expand Down
6 changes: 4 additions & 2 deletions src/utils/get-path-segments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export function getPathSegments(filePath: string): string[] {
segments[0].startsWith('page.') ||
segments[0].startsWith('layout.') ||
segments[0].startsWith('error.') ||
segments[0].startsWith('loading.')
segments[0].startsWith('loading.') ||
segments[0].startsWith('loader.')
) {
segments[0] = '/';
}
Expand All @@ -35,7 +36,8 @@ export function getPathSegments(filePath: string): string[] {
segments[segments.length - 1].startsWith('page.') ||
segments[segments.length - 1].startsWith('layout.') ||
segments[segments.length - 1].startsWith('error.') ||
segments[segments.length - 1].startsWith('loading.')
segments[segments.length - 1].startsWith('loading.') ||
segments[segments.length - 1].startsWith('loader.')
) {
segments.pop();
}
Expand Down
34 changes: 31 additions & 3 deletions src/utils/get-routes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import React from 'react';
import { RouteObject } from 'react-router-dom';
import {
LoaderFunction,
LoaderFunctionArgs,
RouteObject,
} from 'react-router-dom';
import { sortRoutes } from './sort-routes';
import { RouteNode } from '../types';
import { getPathSegments } from './get-path-segments';
Expand All @@ -26,18 +30,41 @@ export function getRoutes(): RouteObject[] {
* pages 디렉토리의 loading.tsx 파일을 모두 가져옵니다.
*/
const loadings = import.meta.glob('/src/pages/**/loading.{jsx,tsx}');
/**
* pages 디렉토리의 loader.ts 파일을 모두 가져옵니다.
*/
const loaders = import.meta.glob('/src/pages/**/loader.{js,ts}');

/** 트리 구조를 생성하기 위한 루트 노드 */
const routeTree: RouteNode = {};

/** 모든 {page | layout | error | loading} 파일을 순회하여 트리 구조에 추가합니다 */
const addElementsToTree = (
glob: Record<string, () => Promise<unknown>>,
type: 'page' | 'layout' | 'error' | 'loading',
type: 'page' | 'layout' | 'error' | 'loading' | 'loader',
) => {
Object.keys(glob).forEach((filePath) => {
const pathSegments = getPathSegments(filePath);
const element = React.lazy(glob[filePath] as any);

let element:
| React.LazyExoticComponent<React.ComponentType<any>>
| LoaderFunction;

if (type === 'loader') {
const moduleLoader = glob[filePath] as () => Promise<{
default: LoaderFunction;
}>;
element = async (args: LoaderFunctionArgs<any>) => {
const module = await moduleLoader();
return module.default(args);
};
} else {
element = React.lazy(
glob[filePath] as unknown as () => Promise<{
default: React.ComponentType<any>;
}>,
);
}

addToRouteTree(routeTree, pathSegments, {
type,
Expand All @@ -50,6 +77,7 @@ export function getRoutes(): RouteObject[] {
addElementsToTree(layouts, 'layout');
addElementsToTree(errors, 'error');
addElementsToTree(loadings, 'loading');
addElementsToTree(loaders, 'loader');

/** 트리 구조를 기반으로 RouteObject 배열 생성 */
const routeObjects = buildRoutesFromTree(routeTree);
Expand Down

0 comments on commit c05222a

Please sign in to comment.