hyperspan 1.0.0-alpha.2 → 1.0.0-alpha.4

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.
Files changed (2) hide show
  1. package/package.json +3 -2
  2. package/src/server.ts +70 -51
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperspan",
3
- "version": "1.0.0-alpha.2",
3
+ "version": "1.0.0-alpha.4",
4
4
  "description": "Hyperspan CLI - for @hyperspan/framework",
5
5
  "type": "module",
6
6
  "public": true,
@@ -39,6 +39,7 @@
39
39
  "devDependencies": {
40
40
  "@types/bun": "^1.3.2",
41
41
  "@types/degit": "^2.8.6",
42
- "@types/node": "^24.10.0"
42
+ "@types/node": "^24.10.0",
43
+ "bun-plugin-tailwind": "^0.1.2"
43
44
  }
44
45
  }
package/src/server.ts CHANGED
@@ -2,8 +2,10 @@ import { Glob } from 'bun';
2
2
  import { createServer, getRunnableRoute, IS_PROD, isValidRoutePath, parsePath } from '@hyperspan/framework';
3
3
  import { CSS_PUBLIC_PATH, CSS_ROUTE_MAP } from '@hyperspan/framework/client/css';
4
4
  import { join } from 'node:path';
5
+ import tailwind from "bun-plugin-tailwind"
5
6
 
6
7
  import type { Hyperspan as HS } from '@hyperspan/framework';
8
+
7
9
  type startConfig = {
8
10
  development?: boolean;
9
11
  };
@@ -37,20 +39,28 @@ export async function startServer(startConfig: startConfig = {}): Promise<HS.Ser
37
39
  const config = await loadConfig();
38
40
  const server = await createServer(config);
39
41
  console.log('[Hyperspan] Adding routes...');
40
- await addRoutes(server, startConfig);
42
+ await addDirectoryAsRoutes(server, 'routes', startConfig);
43
+ console.log('[Hyperspan] Adding actions...');
44
+ await addDirectoryAsRoutes(server, 'actions', startConfig);
41
45
  return server;
42
46
  }
43
47
 
44
- export async function addRoutes(server: HS.Server, startConfig: startConfig) {
48
+ export async function addDirectoryAsRoutes(
49
+ server: HS.Server,
50
+ relativeDirectory: string,
51
+ startConfig: startConfig = {}
52
+ ) {
45
53
  const routesGlob = new Glob('**/*.ts');
46
- const routeFiles: string[] = [];
54
+ const files: string[] = [];
47
55
  const appDir = server._config.appDir || './app';
48
- const routesDir = join(CWD, appDir, 'routes');
56
+ const relativeAppPath = join(appDir, relativeDirectory);
57
+ const directoryPath = join(CWD, appDir, relativeDirectory);
49
58
  const buildDir = join(CWD, '.build');
50
59
  const cssPublicDir = join(CWD, server._config.publicDir, CSS_PUBLIC_PATH);
51
60
 
52
- for await (const file of routesGlob.scan(routesDir)) {
53
- const filePath = join(routesDir, file);
61
+ // Scan directory for TypeScript files
62
+ for await (const file of routesGlob.scan(directoryPath)) {
63
+ const filePath = join(directoryPath, file);
54
64
 
55
65
  // Hidden directories and files start with a double underscore.
56
66
  // These do not get added to the routes. Nothing nested under them gets added to the routes either.
@@ -58,63 +68,72 @@ export async function addRoutes(server: HS.Server, startConfig: startConfig) {
58
68
  continue;
59
69
  }
60
70
 
61
- routeFiles.push(filePath);
71
+ files.push(filePath);
62
72
  }
63
73
 
64
74
  const routeMap: { route: string; file: string }[] = [];
65
- const routes: HS.Route[] = await Promise.all(
66
- routeFiles
67
- .map(async (filePath) => {
68
- const relativePath = filePath.split('app/routes/').pop();
69
- const { path } = parsePath(relativePath ?? '/');
70
-
71
- if (!isValidRoutePath(path)) {
72
- return null;
75
+ const routes: Array<HS.Route> = (await Promise.all(
76
+ files.map(async (filePath) => {
77
+ const relativeFilePath = filePath.split(relativeAppPath).pop() || '';
78
+ if (!isValidRoutePath(relativeFilePath)) {
79
+ return null;
80
+ }
81
+ const module = await import(filePath);
82
+ const route = getRunnableRoute(module);
83
+ const parsedPath = parsePath(relativeFilePath);
84
+
85
+ // If route has a _path() method that returns a meaningful path, use it
86
+ // Otherwise, parse path from file path
87
+ let path = parsedPath.path;
88
+ if (typeof route._path === 'function') {
89
+ const routePath = route._path();
90
+ // If _path() returns a meaningful path (not just '/'), use it
91
+ if (routePath && routePath !== '/') {
92
+ path = routePath;
73
93
  }
74
-
75
- let cssFiles: string[] = [];
76
-
77
- // Build the route just for the CSS files
78
- // Wasteful perhaps to compile the JS also and then just discard it, but it's an easy way to do CSS compilation by route
79
- const buildResult = await Bun.build({
80
- entrypoints: [filePath],
81
- outdir: buildDir,
82
- naming: `app/routes/${path.endsWith('/') ? path + 'index' : path}-[hash].[ext]`,
83
- minify: IS_PROD,
84
- format: 'esm',
85
- target: 'node',
86
- env: 'APP_PUBLIC_*',
87
- });
88
-
89
- // Move CSS files to the public directory
90
- for (const output of buildResult.outputs) {
91
- if (output.path.endsWith('.css')) {
92
- const cssFileName = output.path.split('/').pop()!;
93
- await Bun.write(join(cssPublicDir, cssFileName), Bun.file(output.path));
94
- cssFiles.push(cssFileName);
95
- }
94
+ }
95
+
96
+ let cssFiles: string[] = [];
97
+
98
+ // Build the route just for the CSS files (expensive, but easiest way to do CSS compilation by route)
99
+ // @TODO: Optimize this at some later date... This is O(n) for each route and doesn't scale well for large projects.
100
+ // @TODO: This will also currently re-compile the same CSS file(s) that are included in multiple routes, which is dumb.
101
+ const buildResult = await Bun.build({
102
+ plugins: [tailwind],
103
+ entrypoints: [filePath],
104
+ outdir: buildDir,
105
+ naming: `${relativeAppPath}/${path.endsWith('/') ? path + 'index' : path}-[hash].[ext]`,
106
+ minify: IS_PROD,
107
+ format: 'esm',
108
+ target: 'node',
109
+ });
110
+
111
+ // Move CSS files to the public directory
112
+ for (const output of buildResult.outputs) {
113
+ if (output.path.endsWith('.css')) {
114
+ const cssFileName = output.path.split('/').pop()!;
115
+ await Bun.write(join(cssPublicDir, cssFileName), Bun.file(output.path));
116
+ cssFiles.push(cssFileName);
96
117
  }
118
+ }
97
119
 
98
- const routeModule = await import(filePath);
99
- const route = getRunnableRoute(routeModule);
100
-
101
- // Set route path based on the file path
120
+ // Set route path based on the file path (if not already set)
121
+ if (!route._config.path) {
102
122
  route._config.path = path;
123
+ }
103
124
 
104
- if (cssFiles.length > 0) {
105
- route._config.cssImports = cssFiles;
106
- CSS_ROUTE_MAP.set(path, cssFiles);
107
- }
125
+ if (cssFiles.length > 0) {
126
+ route._config.cssImports = cssFiles;
127
+ CSS_ROUTE_MAP.set(path, cssFiles);
128
+ }
108
129
 
109
- routeMap.push({ route: path, file: filePath.replace(CWD, '') });
130
+ routeMap.push({ route: path, file: filePath.replace(CWD, '') });
110
131
 
111
- return route;
112
- })
113
- .filter((route) => route !== null)
114
- );
132
+ return route;
133
+ })
134
+ )).filter((route) => route !== null);
115
135
 
116
136
  if (startConfig.development) {
117
- console.log('[Hyperspan] Loaded routes:');
118
137
  console.table(routeMap);
119
138
  }
120
139