bun-router 0.7.4-experimental.5 → 0.7.4-experimental.7

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.
@@ -2,6 +2,6 @@ import { Router } from '../../';
2
2
 
3
3
  const router = Router();
4
4
 
5
- router.static('/', './examples/ssr/pages');
5
+ router.static('/page', './examples/ssr/pages');
6
6
 
7
7
  router.serve();
@@ -2,5 +2,6 @@ import { Router } from '..';
2
2
 
3
3
  const r = Router(3001);
4
4
 
5
- r.static('/', './pages');
5
+ r.static('/page', './pages');
6
+
6
7
  r.serve();
@@ -1,4 +1,3 @@
1
- import { splitFilePath } from '../fs/fsys';
2
1
  import path from 'node:path';
3
2
 
4
3
  type File = {
@@ -22,8 +21,8 @@ function createFile(name: string): File {
22
21
  const FileTree = (dir: string) => {
23
22
  const root = createFile(dir);
24
23
 
25
- const addFile = (_path: string) => {
26
- const pathParts = splitFilePath(_path);
24
+ const addFile = (filepath: string) => {
25
+ const pathParts = filepath.split(path.sep);
27
26
  let current = root;
28
27
 
29
28
  for (let i = 0; i < pathParts.length; i++) {
@@ -35,8 +34,8 @@ const FileTree = (dir: string) => {
35
34
  }
36
35
 
37
36
  current.isLast = true;
38
- current.path = _path;
39
- current.extension = path.extname(_path);
37
+ current.path = filepath;
38
+ current.extension = path.extname(filepath);
40
39
  };
41
40
 
42
41
  const getFilesByExtension = (extension: string): string[] => {
@@ -53,9 +52,9 @@ const FileTree = (dir: string) => {
53
52
  return files;
54
53
  };
55
54
 
56
- const getFileByName = (path: string): string | undefined => {
55
+ const getFileByName = (filepath: string): string | undefined => {
57
56
  let current = root;
58
- const pathParts = splitFilePath(path);
57
+ const pathParts = filepath.split(path.sep);
59
58
  for (let i = 0; i < pathParts.length; i++) {
60
59
  const part = pathParts[i];
61
60
  if (current.children.has(part)) {
package/lib/fs/fsys.ts CHANGED
@@ -22,19 +22,18 @@ async function readDir(dirpath: string, handler: (filepath: string, entry: BunFi
22
22
  }
23
23
  }
24
24
 
25
- // get the extension of a file (unnecessary)
26
- function ext(p: string): string {
27
- return path.extname(p);
28
- }
29
-
30
- // split a file path into an array of strings (unnecessary)
31
- function splitFilePath(p: string): string[] {
32
- return p.split(path.sep);
33
- }
34
-
25
+ // resolve module paths relative to the current working directory
35
26
  function resolveModulePath(module: string) {
36
27
  return path.join(process.cwd(), module);
37
28
  }
38
29
 
30
+ function exists(filepath: string): boolean {
31
+ try {
32
+ fs.access(filepath);
33
+ return true;
34
+ } catch (err) {
35
+ return false;
36
+ }
37
+ }
39
38
 
40
- export { readDir, ext, splitFilePath, resolveModulePath };
39
+ export { readDir, resolveModulePath, exists };
@@ -1,36 +1,36 @@
1
1
  const Colors: Record<string,string> = {
2
- reset: "\x1b[0m",
2
+ reset: '\x1b[0m',
3
3
 
4
- // foreground
5
- black: "\x1b[30m",
6
- red: "\x1b[31m",
7
- green: "\x1b[32m",
8
- yellow: "\x1b[33m",
9
- blue: "\x1b[34m",
10
- magenta: "\x1b[35m",
11
- cyan: "\x1b[36m",
12
- white: "\x1b[37m",
4
+ // foreground
5
+ black: '\x1b[30m',
6
+ red: '\x1b[31m',
7
+ green: '\x1b[32m',
8
+ yellow: '\x1b[33m',
9
+ blue: '\x1b[34m',
10
+ magenta: '\x1b[35m',
11
+ cyan: '\x1b[36m',
12
+ white: '\x1b[37m',
13
13
 
14
- // background
15
- bgBlack: "\x1b[40m",
16
- bgRed: "\x1b[41m",
17
- bgGreen: "\x1b[42m",
18
- bgYellow: "\x1b[43m",
19
- bgBlue: "\x1b[44m",
20
- bgMagenta: "\x1b[45m",
21
- bgCyan: "\x1b[46m",
22
- bgWhite: "\x1b[47m",
23
- } as const;
14
+ // background
15
+ bgBlack: '\x1b[40m',
16
+ bgRed: '\x1b[41m',
17
+ bgGreen: '\x1b[42m',
18
+ bgYellow: '\x1b[43m',
19
+ bgBlue: '\x1b[44m',
20
+ bgMagenta: '\x1b[45m',
21
+ bgCyan: '\x1b[46m',
22
+ bgWhite: '\x1b[47m',
23
+ } as const;
24
24
 
25
25
 
26
26
 
27
27
  function color(foreground: string, background: string, message: string) {
28
- const _foreground = Colors[foreground];
29
- const _background = Colors[background];
30
- const reset = Colors.reset;
31
- return `${_foreground}${_background}${message}${reset}`;
28
+ const _foreground = Colors[foreground];
29
+ const _background = Colors[background];
30
+ const reset = Colors.reset;
31
+ return `${_foreground}${_background}${message}${reset}`;
32
32
  }
33
33
 
34
34
 
35
35
 
36
- export { color }
36
+ export { color };
@@ -5,4 +5,4 @@ type BunLogger = {
5
5
  message: (message: string) => void,
6
6
  }
7
7
 
8
- export { BunLogger }
8
+ export { BunLogger };
@@ -9,7 +9,7 @@ _ _
9
9
  |___|___|_|_| |_| |___|___|_| |___|_|
10
10
 
11
11
  `;
12
- const VERSION = '0.7.4-experimental.5';
12
+ const VERSION = '0.7.4-experimental.7';
13
13
  const Logger = (): BunLogger => {
14
14
  return {
15
15
  info: async (statusCode: number, routePath: string, method: string, message?: string) => {
@@ -6,8 +6,8 @@ import { http } from './router';
6
6
  import { ReactNode } from 'react';
7
7
 
8
8
  async function createContext(path: string, route: Route, request: Request): Promise<Context> {
9
- const params = extractParams(path, route);
10
9
  const query = new URLSearchParams(path);
10
+ const params = extractParams(path, route);
11
11
  const formData = isMultiPartForm(request.headers) ? await request.formData() : new FormData();
12
12
 
13
13
  return Promise.resolve({
@@ -21,16 +21,16 @@ async function createContext(path: string, route: Route, request: Request): Prom
21
21
  });
22
22
  }
23
23
 
24
- function extractParams(path: string, route: Route): Map<string, string> {
24
+ function extractParams(pattern: string, route: Route): Map<string, string> {
25
25
  const params: Map<string, string> = new Map();
26
- const pathSegments = path.split('/');
26
+ const pathSegments = pattern.split('/');
27
27
  const routeSegments = route.path.split('/');
28
28
 
29
29
  if (pathSegments.length !== routeSegments.length) return params;
30
30
 
31
31
  for (let i = 0; i < pathSegments.length; i++) {
32
32
  if (routeSegments[i][0] === ':') {
33
- const key = routeSegments[i].replace(':', '');
33
+ const key = routeSegments[i].slice(1);
34
34
  const value = pathSegments[i];
35
35
  params.set(key, value);
36
36
  }
@@ -41,8 +41,7 @@ function extractParams(path: string, route: Route): Map<string, string> {
41
41
 
42
42
  function getContentType(headers: Headers): string {
43
43
  const contentType = headers.get('Content-Type');
44
- if (!contentType) return '';
45
- return contentType;
44
+ return contentType ?? '';
46
45
  }
47
46
 
48
47
  function isMultiPartForm(headers: Headers): boolean {
@@ -1,7 +1,6 @@
1
1
  import { HttpHandler, Route } from './router.d';
2
2
  import { http } from '../http/http';
3
3
  import { createContext } from './context';
4
- import { splitPath } from '../util/strings';
5
4
 
6
5
  const createRoute = (path: string, method: string, handler: HttpHandler): Route => {
7
6
  const route: Route = {
@@ -19,8 +18,8 @@ const createRoute = (path: string, method: string, handler: HttpHandler): Route
19
18
  const RouteTree = () => {
20
19
  const root = createRoute('', 'GET', () => http.notFound());
21
20
 
22
- const addRoute = (path: string, method: string, handler: HttpHandler) => {
23
- const pathParts = splitPath(path);
21
+ const addRoute = (pattern: string, method: string, handler: HttpHandler) => {
22
+ const pathParts = pattern.split('/');
24
23
  let current = root;
25
24
 
26
25
  for (let i = 0; i < pathParts.length; i++) {
@@ -36,11 +35,11 @@ const RouteTree = () => {
36
35
 
37
36
  current.handler = handler;
38
37
  current.isLast = true;
39
- current.path = path;
38
+ current.path = pattern;
40
39
  };
41
40
 
42
- function findRoute(path: string): Route | undefined {
43
- const pathParts = splitPath(path);
41
+ function findRoute(pathname: string): Route | undefined {
42
+ const pathParts = pathname.split('/');
44
43
  let current = root;
45
44
  for (let i = 0; i < pathParts.length; i++) {
46
45
  const part = pathParts[i];
@@ -2,21 +2,34 @@ import path from 'path';
2
2
  import { Database } from 'bun:sqlite';
3
3
  import { Route, BunRouter, RouterOptions, Options, HttpHandler } from './router.d';
4
4
  import { httpStatusCodes } from '../http/status';
5
- import { readDir, resolveModulePath } from '../fs/fsys';
5
+ import { readDir, exists } from '../fs/fsys';
6
6
  import { Logger, startMessage } from '../logger/logger';
7
7
  import { http } from '../http/http';
8
- import { RouteTree } from './tree';
8
+ import { RouteTree } from './routeTree';
9
9
  import { createContext } from './context';
10
10
 
11
11
  const Router: BunRouter = (port?: number | string, options?: RouterOptions<Options>) => {
12
12
  const { addRoute, findRoute } = RouteTree();
13
13
  const logger = Logger();
14
14
 
15
- async function loadComponent(name: string) {
16
- const module = await import(name);
15
+ async function loadComponent(root: string, name: string) {
16
+ const module = await import(path.join(process.cwd(), root, name));
17
17
  return module.default;
18
18
  }
19
19
 
20
+ function normalizePath(pattern: string, pathname: string) {
21
+ const extension = path.extname(pathname);
22
+ let base = path.basename(pathname);
23
+
24
+ if (extension === '.html' || extension === '.tsx') base = base.replace(extension, '');
25
+
26
+ let patternPath = [pattern, base].join('/');
27
+
28
+ if (base === 'index') patternPath = pattern;
29
+
30
+ return { patternPath, extension, base }
31
+ }
32
+
20
33
  return {
21
34
  // add a route to the router tree
22
35
  add: (pattern, method, callback) => { addRoute(pattern, method, callback); },
@@ -26,35 +39,23 @@ const Router: BunRouter = (port?: number | string, options?: RouterOptions<Optio
26
39
  delete: (pattern, callback) => { addRoute(pattern, 'DELETE', callback); },
27
40
 
28
41
  // add static routes to the router tree
29
- // .tsx and .html are rendered as components
42
+ // .tsx and .html are rendered as components, or pages
30
43
  // all other file extensions are served as files
31
44
  // the root directory is traversed recursively
32
45
  static: async (pattern: string, root: string) => {
46
+ if (!exists(root)) console.log(`Cannot find directory ${root}`);
33
47
  await readDir(root, async (fp) => {
34
- const ext = path.extname(fp);
35
-
36
- let base = path.basename(fp);
37
-
38
- //FIXME: this can be improved
39
- if (ext === '.html' || ext === '.tsx') base = base.replace(ext, '');
40
-
41
- if (pattern[0] !== '/') pattern = '/' + pattern;
42
-
43
- let patternPath = pattern + '/' + base;
44
-
45
- if (base === 'index') patternPath = pattern;
46
-
47
- const purePath = path.join(root, patternPath);
48
+ const { patternPath, extension, base } = normalizePath(pattern, fp);
48
49
 
49
50
  const route: Route = {
50
51
  children: new Map(),
51
52
  dynamicPath: '',
52
53
  isLast: true,
53
- path: patternPath,
54
+ path: patternPath.slice(1),
54
55
  method: 'GET',
55
56
  handler: async () => {
56
- if (ext === '.tsx') {
57
- const component = await loadComponent(resolveModulePath(purePath.split('.')[0]));
57
+ if (extension === '.tsx') {
58
+ const component = await loadComponent(root, base);
58
59
  return await http.render(component());
59
60
  } else {
60
61
  return await http.file(200, fp);
@@ -62,6 +63,8 @@ const Router: BunRouter = (port?: number | string, options?: RouterOptions<Optio
62
63
  },
63
64
  };
64
65
 
66
+ console.log(route.path);
67
+
65
68
  addRoute(route.path, 'GET', route.handler);
66
69
  });
67
70
  },
@@ -75,7 +78,7 @@ const Router: BunRouter = (port?: number | string, options?: RouterOptions<Optio
75
78
  ...options,
76
79
  async fetch(req) {
77
80
  const url = new URL(req.url);
78
- const path = url.pathname;
81
+ const pathname = url.pathname;
79
82
 
80
83
  // set the database
81
84
  if (options) {
@@ -83,16 +86,16 @@ const Router: BunRouter = (port?: number | string, options?: RouterOptions<Optio
83
86
  opts.db = o.db;
84
87
  }
85
88
 
86
- const route = findRoute(path);
89
+ const route = findRoute(pathname);
87
90
 
88
- // if the route exists, execute the handler
91
+ // if the route exists, call the handler
89
92
  if (route) {
90
93
  if (route.method !== req.method) {
91
94
  logger.info(405, url.pathname, req.method, httpStatusCodes[405]);
92
95
  return Promise.resolve(http.methodNotAllowed());
93
96
  }
94
97
 
95
- const context = await createContext(path, route, req);
98
+ const context = await createContext(pathname, route, req);
96
99
  context.db = new Database(opts.db);
97
100
 
98
101
  const response = await route.handler(context);
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "peerDependencies": {
15
15
  "typescript": "^5.0.0"
16
16
  },
17
- "version": "0.7.4-experimental.5",
17
+ "version": "0.7.4-experimental.7",
18
18
  "dependencies": {
19
19
  "@types/react": "^18.2.22",
20
20
  "eslint-plugin-react-hooks": "^4.6.0",
package/tsconfig.json CHANGED
@@ -19,7 +19,6 @@
19
19
  "bun-types" // add Bun global
20
20
  ],
21
21
  "paths": {
22
- "examples/*": ["./examples/*"],
23
22
  }
24
23
  },
25
24
  }
@@ -1,5 +0,0 @@
1
- import { resolveModules } from './resolver';
2
-
3
- const modules = await resolveModules('../lib/resolver/testdir');
4
-
5
- console.log(modules.length);
@@ -1,43 +0,0 @@
1
- import { FileTree } from '../fs/filetree';
2
- import { readDir } from '../fs/fsys';
3
- import { ComponentType } from 'react';
4
-
5
- async function createFileTree(root: string) {
6
- const tree = FileTree(root);
7
-
8
- await readDir(root, (fp) => {
9
- tree.addFile(fp);
10
- });
11
-
12
- return tree;
13
- }
14
-
15
- async function resolveModules(root: string) {
16
-
17
- if (!await doesExist(root)) {
18
- throw new Error(`Directory ${root} does not exist`);
19
- }
20
-
21
- const tree = await createFileTree(root);
22
- const files = tree.getFilesByExtension('.tsx');
23
- const modules: ComponentType[] = [];
24
-
25
- for (const file of files) {
26
- const module = await import(file);
27
- modules.push(module);
28
- }
29
- return modules;
30
- }
31
-
32
- async function doesExist(root: string): Promise<boolean> {
33
- const file = Bun.file(root);
34
- return await file.exists();
35
- }
36
-
37
- async function isDir(root: string): Promise<boolean> {
38
- const file = Bun.file(root);
39
- return await file.isDir();
40
- }
41
-
42
-
43
- export { resolveModules };
@@ -1,5 +0,0 @@
1
- import React from 'react';
2
-
3
- export default function foo() {
4
- return <div>foo</div>;
5
- }
@@ -1,3 +0,0 @@
1
- const splitPath = (path: string): string[] => path.split('/').filter(Boolean);
2
-
3
- export { splitPath }