fs-router-dom 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/FileRouter.d.ts +9 -0
- package/dist/FileRouter.js +77 -0
- package/dist/FileRouter.js.map +1 -0
- package/dist/FileRouter.test.d.ts +1 -0
- package/dist/FileRouter.test.js +30 -0
- package/dist/FileRouter.test.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/setupTests.d.ts +1 -0
- package/dist/setupTests.js +7 -0
- package/dist/setupTests.js.map +1 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# react-file-router
|
|
2
|
+
|
|
3
|
+
Lightweight file-based router for Vite + React Router DOM, like Next.js.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
```
|
|
7
|
+
npm install react-file-router
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
### `<FileRouter />`
|
|
13
|
+
|
|
14
|
+
The `FileRouter` component dynamically creates `react-router-dom` routes from a list of file-based modules. It requires being a child of a `BrowserRouter` (or another `react-router` router).
|
|
15
|
+
|
|
16
|
+
```jsx
|
|
17
|
+
// App.tsx
|
|
18
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
19
|
+
import { FileRouter } from 'react-file-router';
|
|
20
|
+
|
|
21
|
+
const routes = import.meta.glob('./pages/**/*.tsx');
|
|
22
|
+
|
|
23
|
+
function App() {
|
|
24
|
+
return (
|
|
25
|
+
<BrowserRouter basename="/app">
|
|
26
|
+
<FileRouter routes={routes} />
|
|
27
|
+
</BrowserRouter>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default App;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### `<FullFileRouter />`
|
|
35
|
+
|
|
36
|
+
The `FullFileRouter` component is a "batteries-included" component for users who want to quickly set up file-based routing without configuring `react-router-dom`'s `BrowserRouter` separately. It instantiates a `BrowserRouter` and places the `FileRouter` inside it, handling the setup in one step.
|
|
37
|
+
|
|
38
|
+
`FullFileRouter` accepts two types of props:
|
|
39
|
+
- `routes`: The same `routes` object required by `FileRouter`.
|
|
40
|
+
- All props accepted by `react-router-dom`'s `BrowserRouter` (e.g., `basename`, `window`).
|
|
41
|
+
|
|
42
|
+
```jsx
|
|
43
|
+
// App.tsx
|
|
44
|
+
import { FullFileRouter } from 'react-file-router';
|
|
45
|
+
|
|
46
|
+
const routes = import.meta.glob('./pages/**/*.tsx');
|
|
47
|
+
|
|
48
|
+
function App() {
|
|
49
|
+
return <FullFileRouter routes={routes} basename="/app" />;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default App;
|
|
53
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BrowserRouterProps } from 'react-router-dom';
|
|
2
|
+
interface FileRouterProps {
|
|
3
|
+
routes: Record<string, () => Promise<unknown>>;
|
|
4
|
+
}
|
|
5
|
+
export declare function FileRouter({ routes }: FileRouterProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
interface FullFileRouterProps extends FileRouterProps, Omit<BrowserRouterProps, 'children'> {
|
|
7
|
+
}
|
|
8
|
+
export declare function FullFileRouter({ routes, ...browserProps }: FullFileRouterProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FileRouter = FileRouter;
|
|
37
|
+
exports.FullFileRouter = FullFileRouter;
|
|
38
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
39
|
+
const react_1 = __importStar(require("react"));
|
|
40
|
+
const react_router_dom_1 = require("react-router-dom");
|
|
41
|
+
/**
|
|
42
|
+
* Transforms a file path from the format './pages/users/[id].tsx'
|
|
43
|
+
* to the format '/users/:id' for react-router-dom.
|
|
44
|
+
* @param filePath The file path to transform.
|
|
45
|
+
* @returns The transformed route path.
|
|
46
|
+
*/
|
|
47
|
+
function transformFilePathToRoutePath(filePath) {
|
|
48
|
+
// Remove everything up to and including /pages/
|
|
49
|
+
let path = filePath.replace(/.*\/pages\//, '/');
|
|
50
|
+
// Remove .js, .jsx, .ts, .tsx extension
|
|
51
|
+
path = path.replace(/\.[jt]sx?$/, '');
|
|
52
|
+
// Handle index routes
|
|
53
|
+
if (path.endsWith('/index')) {
|
|
54
|
+
path = path.slice(0, -6); // Remove /index
|
|
55
|
+
}
|
|
56
|
+
// Handle root route
|
|
57
|
+
if (path === '') {
|
|
58
|
+
path = '/';
|
|
59
|
+
}
|
|
60
|
+
// Convert [param] to :param for dynamic routes
|
|
61
|
+
path = path.replace(/\[(.*?)\]/g, ':$1');
|
|
62
|
+
return path;
|
|
63
|
+
}
|
|
64
|
+
function FileRouter({ routes }) {
|
|
65
|
+
return ((0, jsx_runtime_1.jsxs)(react_router_dom_1.Routes, { children: [Object.entries(routes).map(([filePath, importFn]) => {
|
|
66
|
+
const path = transformFilePathToRoutePath(filePath);
|
|
67
|
+
const Component = react_1.default.lazy(async () => {
|
|
68
|
+
const module = await importFn();
|
|
69
|
+
return { default: module.default };
|
|
70
|
+
});
|
|
71
|
+
return ((0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: path, element: (0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: (0, jsx_runtime_1.jsx)("div", { children: "Loading..." }), children: (0, jsx_runtime_1.jsx)(Component, {}) }) }, path));
|
|
72
|
+
}), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "*", element: (0, jsx_runtime_1.jsx)("div", { children: "404 - Not Found" }) })] }));
|
|
73
|
+
}
|
|
74
|
+
function FullFileRouter({ routes, ...browserProps }) {
|
|
75
|
+
return ((0, jsx_runtime_1.jsx)(react_router_dom_1.BrowserRouter, { ...browserProps, children: (0, jsx_runtime_1.jsx)(FileRouter, { routes: routes }) }));
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=FileRouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileRouter.js","sourceRoot":"","sources":["../src/FileRouter.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,gCA0BC;AAID,wCAMC;;AAxED,+CAAwC;AACxC,uDAAoF;AAMpF;;;;;GAKG;AACH,SAAS,4BAA4B,CAAC,QAAgB;IACpD,gDAAgD;IAChD,IAAI,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;IAEhD,wCAAwC;IACxC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAEtC,sBAAsB;IACtB,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,gBAAgB;IAC5C,CAAC;IAED,oBAAoB;IACpB,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QAChB,IAAI,GAAG,GAAG,CAAC;IACb,CAAC;IAED,+CAA+C;IAC/C,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IAEzC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAgB,UAAU,CAAC,EAAE,MAAM,EAAmB;IACpD,OAAO,CACL,wBAAC,yBAAM,eACJ,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE;gBACnD,MAAM,IAAI,GAAG,4BAA4B,CAAC,QAAQ,CAAC,CAAC;gBAEpD,MAAM,SAAS,GAAG,eAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;oBACtC,MAAM,MAAM,GAAG,MAAM,QAAQ,EAAE,CAAC;oBAChC,OAAO,EAAE,OAAO,EAAG,MAAgD,CAAC,OAAO,EAAE,CAAC;gBAChF,CAAC,CAAC,CAAC;gBACH,OAAO,CACL,uBAAC,wBAAK,IAEJ,IAAI,EAAE,IAAI,EACV,OAAO,EACL,uBAAC,gBAAQ,IAAC,QAAQ,EAAE,yDAAqB,YACvC,uBAAC,SAAS,KAAG,GACJ,IALR,IAAI,CAOT,CACH,CAAC;YACJ,CAAC,CAAC,EAEF,uBAAC,wBAAK,IAAC,IAAI,EAAC,GAAG,EAAC,OAAO,EAAE,8DAA0B,GAAI,IAChD,CACV,CAAC;AACJ,CAAC;AAID,SAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,YAAY,EAAuB;IAC7E,OAAO,CACL,uBAAC,gCAAa,OAAK,YAAY,YAC7B,uBAAC,UAAU,IAAC,MAAM,EAAE,MAAM,GAAI,GAChB,CACjB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
|
+
const react_1 = require("@testing-library/react");
|
|
5
|
+
const react_router_dom_1 = require("react-router-dom");
|
|
6
|
+
const FileRouter_1 = require("./FileRouter");
|
|
7
|
+
// Mock components
|
|
8
|
+
const Home = () => (0, jsx_runtime_1.jsx)("div", { children: "Home" });
|
|
9
|
+
const About = () => (0, jsx_runtime_1.jsx)("div", { children: "About" });
|
|
10
|
+
const User = () => (0, jsx_runtime_1.jsx)("div", { children: "User" });
|
|
11
|
+
const routes = {
|
|
12
|
+
'./pages/index.tsx': () => Promise.resolve({ default: Home }),
|
|
13
|
+
'./pages/about.tsx': () => Promise.resolve({ default: About }),
|
|
14
|
+
'./pages/users/[id].tsx': () => Promise.resolve({ default: User }),
|
|
15
|
+
};
|
|
16
|
+
describe('FileRouter', () => {
|
|
17
|
+
it('should render the correct component for a static route', async () => {
|
|
18
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(react_router_dom_1.MemoryRouter, { initialEntries: ['/about'], children: (0, jsx_runtime_1.jsx)(FileRouter_1.FileRouter, { routes: routes }) }));
|
|
19
|
+
expect(await react_1.screen.findByText('About')).toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
it('should render the correct component for the root route', async () => {
|
|
22
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(react_router_dom_1.MemoryRouter, { initialEntries: ['/'], children: (0, jsx_runtime_1.jsx)(FileRouter_1.FileRouter, { routes: routes }) }));
|
|
23
|
+
expect(await react_1.screen.findByText('Home')).toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
it('should render the correct component for a dynamic route', async () => {
|
|
26
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(react_router_dom_1.MemoryRouter, { initialEntries: ['/users/123'], children: (0, jsx_runtime_1.jsx)(FileRouter_1.FileRouter, { routes: routes }) }));
|
|
27
|
+
expect(await react_1.screen.findByText('User')).toBeInTheDocument();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=FileRouter.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileRouter.test.js","sourceRoot":"","sources":["../src/FileRouter.test.tsx"],"names":[],"mappings":";;;AACA,kDAAwD;AACxD,uDAAgD;AAChD,6CAA0C;AAE1C,kBAAkB;AAClB,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,mDAAe,CAAC;AACnC,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,oDAAgB,CAAC;AACrC,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,mDAAe,CAAC;AAEnC,MAAM,MAAM,GAAG;IACb,mBAAmB,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7D,mBAAmB,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9D,wBAAwB,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;CACnE,CAAC;AAEF,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,IAAA,cAAM,EACJ,uBAAC,+BAAY,IAAC,cAAc,EAAE,CAAC,QAAQ,CAAC,YACtC,uBAAC,uBAAU,IAAC,MAAM,EAAE,MAAM,GAAI,GACjB,CAChB,CAAC;QACF,MAAM,CAAC,MAAM,cAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,IAAA,cAAM,EACJ,uBAAC,+BAAY,IAAC,cAAc,EAAE,CAAC,GAAG,CAAC,YACjC,uBAAC,uBAAU,IAAC,MAAM,EAAE,MAAM,GAAI,GACjB,CAChB,CAAC;QACF,MAAM,CAAC,MAAM,cAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,IAAA,cAAM,EACJ,uBAAC,+BAAY,IAAC,cAAc,EAAE,CAAC,YAAY,CAAC,YAC1C,uBAAC,uBAAU,IAAC,MAAM,EAAE,MAAM,GAAI,GACjB,CAChB,CAAC;QACF,MAAM,CAAC,MAAM,cAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC;IAC9D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FileRouter, FullFileRouter } from './FileRouter';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FullFileRouter = exports.FileRouter = void 0;
|
|
4
|
+
var FileRouter_1 = require("./FileRouter");
|
|
5
|
+
Object.defineProperty(exports, "FileRouter", { enumerable: true, get: function () { return FileRouter_1.FileRouter; } });
|
|
6
|
+
Object.defineProperty(exports, "FullFileRouter", { enumerable: true, get: function () { return FileRouter_1.FullFileRouter; } });
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CAA0D;AAAjD,wGAAA,UAAU,OAAA;AAAE,4GAAA,cAAc,OAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
require("@testing-library/jest-dom");
|
|
4
|
+
const util_1 = require("util");
|
|
5
|
+
global.TextEncoder = util_1.TextEncoder;
|
|
6
|
+
global.TextDecoder = util_1.TextDecoder;
|
|
7
|
+
//# sourceMappingURL=setupTests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupTests.js","sourceRoot":"","sources":["../src/setupTests.ts"],"names":[],"mappings":";;AAAA,qCAAmC;AACnC,+BAAgD;AAEhD,MAAM,CAAC,WAAW,GAAG,kBAAW,CAAC;AACjC,MAAM,CAAC,WAAW,GAAG,kBAAkB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fs-router-dom",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Lightweight file-based router for Vite + react-router-dom, inspired by Next.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"react": ">=17.0.0",
|
|
17
|
+
"react-dom": ">=17.0.0",
|
|
18
|
+
"react-router-dom": ">=6.0.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@testing-library/jest-dom": "^6.4.8",
|
|
22
|
+
"@testing-library/react": "^16.0.0",
|
|
23
|
+
"@types/jest": "^29.5.12",
|
|
24
|
+
"@types/react": "^18.3.27",
|
|
25
|
+
"jest": "^29.7.0",
|
|
26
|
+
"jest-environment-jsdom": "^30.2.0",
|
|
27
|
+
"ts-jest": "^29.2.2",
|
|
28
|
+
"typescript": "^5.9.3"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"react",
|
|
32
|
+
"router",
|
|
33
|
+
"file-based",
|
|
34
|
+
"vite"
|
|
35
|
+
],
|
|
36
|
+
"author": "Your Name",
|
|
37
|
+
"license": "MIT"
|
|
38
|
+
}
|