diff --git a/README.md b/README.md
index d0e9f2d..af1fedb 100644
--- a/README.md
+++ b/README.md
@@ -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.
+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**
diff --git a/docs/README.md b/docs/README.md
index 99c8b04..11c0e64 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -177,6 +177,23 @@ export default function HomeLoading() {
}
```
+#### **7. loader 지원**
+
+폴더 내에 `loader.ts` 파일을 추가하여 해당 경로에 **loader**에 대한 처리를 수행할 수 있습니다.
+자세한 내용은 [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();
+```
+
### **📄 기여 방법**
이 프로젝트에 기여하고 싶으시다면, 다음 절차를 따라주세요:
diff --git a/package.json b/package.json
index 4af1bd6..39879ff 100644
--- a/package.json
+++ b/package.json
@@ -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": [
diff --git a/src/file-router.tsx b/src/file-router.tsx
index 1af122e..6860e6d 100644
--- a/src/file-router.tsx
+++ b/src/file-router.tsx
@@ -18,9 +18,5 @@ export function FileRouter() {
const router = React.useMemo(() => createBrowserRouter(routes), [routes]);
- return (
- Loading,,,}>
-
-
- );
+ return ;
}
diff --git a/src/types/index.ts b/src/types/index.ts
index 89a47df..65c8918 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -1,21 +1,25 @@
+import { LoaderFunction } from 'react-router-dom';
+
export type RouteNode = {
/** 라우터 path */
path?: string;
/** 라우터 component */
- element?: React.LazyExoticComponent;
+ element?: React.LazyExoticComponent>;
/** layout component */
- layoutElement?: React.LazyExoticComponent;
+ layoutElement?: React.LazyExoticComponent>;
/** page component */
- pageElement?: React.LazyExoticComponent;
+ pageElement?: React.LazyExoticComponent>;
/** error component */
- errorElement?: React.LazyExoticComponent;
+ errorElement?: React.LazyExoticComponent>;
/** loading component */
- loadingElement?: React.LazyExoticComponent;
+ loadingElement?: React.LazyExoticComponent>;
+ /** loader function */
+ loaderFn?: LoaderFunction;
/** children 라우트 */
children?: Record;
};
export type NodeData = {
- type: 'page' | 'layout' | 'error' | 'loading';
- element: React.LazyExoticComponent;
+ type: 'page' | 'layout' | 'error' | 'loading' | 'loader';
+ element: React.LazyExoticComponent> | LoaderFunction;
};
diff --git a/src/utils/add-tree.ts b/src/utils/add-tree.ts
index 1ab994f..e8d3c67 100644
--- a/src/utils/add-tree.ts
+++ b/src/utils/add-tree.ts
@@ -1,3 +1,4 @@
+import { LoaderFunction } from 'react-router-dom';
import { NodeData, RouteNode } from '../types';
/**
@@ -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
+ >;
return;
}
/** 루트 에러면 트리의 최상위에 errorElement를 설정 */
if (isRootLayout && nodeData.type === 'error') {
- tree.errorElement = nodeData.element;
+ tree.errorElement = nodeData.element as React.LazyExoticComponent<
+ React.ComponentType
+ >;
return;
}
/** 루트 로딩이면 트리의 최상위에 loadingElement를 설정 */
if (isRootLayout && nodeData.type === 'loading') {
- tree.loadingElement = nodeData.element;
+ tree.loadingElement = nodeData.element as React.LazyExoticComponent<
+ React.ComponentType
+ >;
+ return;
+ }
+ /** 루트 loader면 트리의 최상위에 loaderFn를 설정 */
+ if (isRootLayout && nodeData.type === 'loader') {
+ tree.loaderFn = nodeData.element as LoaderFunction;
return;
}
@@ -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
+ >;
+ break;
+ case 'layout':
+ current.layoutElement = nodeData.element as React.LazyExoticComponent<
+ React.ComponentType
+ >;
+ break;
+ case 'error':
+ current.errorElement = nodeData.element as React.LazyExoticComponent<
+ React.ComponentType
+ >;
+ break;
+ case 'loading':
+ current.loadingElement =
+ nodeData.element as React.LazyExoticComponent<
+ React.ComponentType
+ >;
+ break;
+ case 'loader':
+ current.loaderFn = nodeData.element as LoaderFunction;
+ break;
}
}
}
diff --git a/src/utils/build-tree.ts b/src/utils/build-tree.ts
index 10c9972..6366301 100644
--- a/src/utils/build-tree.ts
+++ b/src/utils/build-tree.ts
@@ -24,6 +24,8 @@ export function buildRoutesFromTree(
? React.createElement(node.loadingElement)
: undefined;
+ const loaderFn = node.loaderFn ? node.loaderFn : undefined;
+
if (node.pageElement) {
childRoutes.push({
index: true,
@@ -31,6 +33,7 @@ export function buildRoutesFromTree(
errorElement: node.errorElement
? wrapWithSuspense(node.errorElement)
: undefined,
+ loader: loaderFn,
});
}
diff --git a/src/utils/create-route-object.ts b/src/utils/create-route-object.ts
index dc2d45e..a7004f8 100644
--- a/src/utils/create-route-object.ts
+++ b/src/utils/create-route-object.ts
@@ -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);
@@ -40,6 +45,7 @@ export function createRouteObject(node: RouteNode, path?: string): RouteObject {
index: true,
element: pageElement,
errorElement: route.errorElement,
+ loader: route.loader,
},
];
} else {
diff --git a/src/utils/get-path-segments.ts b/src/utils/get-path-segments.ts
index d7cf391..4f9c665 100644
--- a/src/utils/get-path-segments.ts
+++ b/src/utils/get-path-segments.ts
@@ -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] = '/';
}
@@ -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();
}
diff --git a/src/utils/get-routes.ts b/src/utils/get-routes.ts
index 2f2eeee..97c4c8d 100644
--- a/src/utils/get-routes.ts
+++ b/src/utils/get-routes.ts
@@ -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';
@@ -26,6 +30,10 @@ 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 = {};
@@ -33,11 +41,30 @@ export function getRoutes(): RouteObject[] {
/** 모든 {page | layout | error | loading} 파일을 순회하여 트리 구조에 추가합니다 */
const addElementsToTree = (
glob: Record Promise>,
- 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>
+ | LoaderFunction;
+
+ if (type === 'loader') {
+ const moduleLoader = glob[filePath] as () => Promise<{
+ default: LoaderFunction;
+ }>;
+ element = async (args: LoaderFunctionArgs) => {
+ const module = await moduleLoader();
+ return module.default(args);
+ };
+ } else {
+ element = React.lazy(
+ glob[filePath] as unknown as () => Promise<{
+ default: React.ComponentType;
+ }>,
+ );
+ }
addToRouteTree(routeTree, pathSegments, {
type,
@@ -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);