@mauroandre/velojs 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/LICENSE +21 -0
- package/README.md +1049 -0
- package/bin/velojs.js +15 -0
- package/package.json +120 -0
- package/src/cli.ts +83 -0
- package/src/client.tsx +79 -0
- package/src/components.tsx +155 -0
- package/src/config.ts +29 -0
- package/src/cookie.ts +7 -0
- package/src/factory.ts +1 -0
- package/src/hooks.tsx +266 -0
- package/src/index.ts +19 -0
- package/src/init.ts +177 -0
- package/src/server.tsx +347 -0
- package/src/types.ts +39 -0
- package/src/vite.ts +937 -0
package/bin/velojs.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const cliPath = resolve(__dirname, "../src/cli.ts");
|
|
9
|
+
|
|
10
|
+
const result = spawnSync("npx", ["tsx", cliPath, ...process.argv.slice(2)], {
|
|
11
|
+
stdio: "inherit",
|
|
12
|
+
cwd: process.cwd(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
process.exit(result.status ?? 0);
|
package/package.json
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mauroandre/velojs",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Fullstack web framework built on Hono + Preact with Vite-powered AST transforms",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"framework",
|
|
8
|
+
"fullstack",
|
|
9
|
+
"hono",
|
|
10
|
+
"preact",
|
|
11
|
+
"vite",
|
|
12
|
+
"ssr",
|
|
13
|
+
"typescript"
|
|
14
|
+
],
|
|
15
|
+
"author": "Mauro André Silva",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/mauro-andre/velojs.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/mauro-andre/velojs#readme",
|
|
22
|
+
"bin": {
|
|
23
|
+
"velojs": "./bin/velojs.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"src/",
|
|
27
|
+
"bin/",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:watch": "vitest"
|
|
35
|
+
},
|
|
36
|
+
"main": "./src/index.ts",
|
|
37
|
+
"types": "./src/index.ts",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./src/index.ts",
|
|
41
|
+
"import": "./src/index.ts"
|
|
42
|
+
},
|
|
43
|
+
"./server": {
|
|
44
|
+
"types": "./src/server.tsx",
|
|
45
|
+
"import": "./src/server.tsx"
|
|
46
|
+
},
|
|
47
|
+
"./client": {
|
|
48
|
+
"types": "./src/client.tsx",
|
|
49
|
+
"import": "./src/client.tsx"
|
|
50
|
+
},
|
|
51
|
+
"./hooks": {
|
|
52
|
+
"types": "./src/hooks.tsx",
|
|
53
|
+
"import": "./src/hooks.tsx"
|
|
54
|
+
},
|
|
55
|
+
"./cookie": {
|
|
56
|
+
"types": "./src/cookie.ts",
|
|
57
|
+
"import": "./src/cookie.ts"
|
|
58
|
+
},
|
|
59
|
+
"./factory": {
|
|
60
|
+
"types": "./src/factory.ts",
|
|
61
|
+
"import": "./src/factory.ts"
|
|
62
|
+
},
|
|
63
|
+
"./vite": {
|
|
64
|
+
"types": "./src/vite.ts",
|
|
65
|
+
"import": "./src/vite.ts"
|
|
66
|
+
},
|
|
67
|
+
"./config": {
|
|
68
|
+
"types": "./src/config.ts",
|
|
69
|
+
"import": "./src/config.ts"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"typesVersions": {
|
|
73
|
+
"*": {
|
|
74
|
+
"server": [
|
|
75
|
+
"./src/server.tsx"
|
|
76
|
+
],
|
|
77
|
+
"client": [
|
|
78
|
+
"./src/client.tsx"
|
|
79
|
+
],
|
|
80
|
+
"hooks": [
|
|
81
|
+
"./src/hooks.tsx"
|
|
82
|
+
],
|
|
83
|
+
"cookie": [
|
|
84
|
+
"./src/cookie.ts"
|
|
85
|
+
],
|
|
86
|
+
"factory": [
|
|
87
|
+
"./src/factory.ts"
|
|
88
|
+
],
|
|
89
|
+
"vite": [
|
|
90
|
+
"./src/vite.ts"
|
|
91
|
+
],
|
|
92
|
+
"config": [
|
|
93
|
+
"./src/config.ts"
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"dependencies": {
|
|
98
|
+
"@babel/generator": "^7.28.5",
|
|
99
|
+
"@babel/parser": "^7.28.5",
|
|
100
|
+
"@babel/traverse": "^7.28.5",
|
|
101
|
+
"@babel/types": "^7.28.5",
|
|
102
|
+
"@hono/node-server": "^1.19.0",
|
|
103
|
+
"@hono/vite-dev-server": "^0.25.0",
|
|
104
|
+
"@preact/preset-vite": "^2.10.0",
|
|
105
|
+
"@preact/signals": "^2.8.0",
|
|
106
|
+
"hono": "^4.12.0",
|
|
107
|
+
"preact": "^10.29.0",
|
|
108
|
+
"preact-render-to-string": "^6.6.0",
|
|
109
|
+
"tsx": "^4.19.0",
|
|
110
|
+
"vite": "^7.3.0",
|
|
111
|
+
"wouter-preact": "^3.9.0"
|
|
112
|
+
},
|
|
113
|
+
"devDependencies": {
|
|
114
|
+
"@types/babel__generator": "^7.27.0",
|
|
115
|
+
"@types/babel__traverse": "^7.28.0",
|
|
116
|
+
"@types/node": "^25.5.2",
|
|
117
|
+
"typescript": "^6.0.2",
|
|
118
|
+
"vitest": "^3.2.1"
|
|
119
|
+
}
|
|
120
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const command = args[0];
|
|
8
|
+
|
|
9
|
+
const runCommand = (cmd: string, cmdArgs: string[] = []) => {
|
|
10
|
+
const result = spawnSync(cmd, cmdArgs, {
|
|
11
|
+
stdio: "inherit",
|
|
12
|
+
cwd: process.cwd(),
|
|
13
|
+
});
|
|
14
|
+
process.exit(result.status ?? 0);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const runVite = () => {
|
|
18
|
+
runCommand("npx", ["vite", ...args.slice(1)]);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const runBuild = () => {
|
|
22
|
+
console.log("Building client...");
|
|
23
|
+
spawnSync("npx", ["vite", "build"], { stdio: "inherit", cwd: process.cwd() });
|
|
24
|
+
|
|
25
|
+
console.log("Building server...");
|
|
26
|
+
spawnSync("npx", ["vite", "build", "--mode", "server"], { stdio: "inherit", cwd: process.cwd() });
|
|
27
|
+
|
|
28
|
+
console.log("Build complete!");
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const runStart = () => {
|
|
32
|
+
const serverPath = resolve(process.cwd(), "dist/server.js");
|
|
33
|
+
runCommand("node", [serverPath]);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const showHelp = () => {
|
|
37
|
+
console.log(`
|
|
38
|
+
VeloJS CLI
|
|
39
|
+
|
|
40
|
+
Usage:
|
|
41
|
+
velojs <command>
|
|
42
|
+
|
|
43
|
+
Commands:
|
|
44
|
+
init Create a new VeloJS project
|
|
45
|
+
dev Start development server
|
|
46
|
+
build Build for production (client + server)
|
|
47
|
+
start Start production server
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
velojs init my-app
|
|
51
|
+
velojs dev
|
|
52
|
+
velojs build
|
|
53
|
+
velojs start
|
|
54
|
+
`);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
switch (command) {
|
|
58
|
+
case "init": {
|
|
59
|
+
const { runInit } = await import("./init.js");
|
|
60
|
+
await runInit(args[1]);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case "dev":
|
|
64
|
+
runVite();
|
|
65
|
+
break;
|
|
66
|
+
case "build":
|
|
67
|
+
runBuild();
|
|
68
|
+
break;
|
|
69
|
+
case "start":
|
|
70
|
+
runStart();
|
|
71
|
+
break;
|
|
72
|
+
case "help":
|
|
73
|
+
case "--help":
|
|
74
|
+
case "-h":
|
|
75
|
+
showHelp();
|
|
76
|
+
break;
|
|
77
|
+
default:
|
|
78
|
+
if (command) {
|
|
79
|
+
console.error(`Unknown command: ${command}`);
|
|
80
|
+
}
|
|
81
|
+
showHelp();
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
package/src/client.tsx
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { hydrate } from "preact";
|
|
2
|
+
import { Router, Route, Switch } from "wouter-preact";
|
|
3
|
+
import type { VNode } from "preact";
|
|
4
|
+
import type { RouteNode, AppRoutes } from "./types.js";
|
|
5
|
+
|
|
6
|
+
// ============================================
|
|
7
|
+
// CLIENT OPTIONS
|
|
8
|
+
// ============================================
|
|
9
|
+
|
|
10
|
+
export interface StartClientOptions {
|
|
11
|
+
routes: AppRoutes;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ============================================
|
|
15
|
+
// BUILD ROUTES - Gera rotas do wouter recursivamente
|
|
16
|
+
// ============================================
|
|
17
|
+
|
|
18
|
+
const buildRoutes = (nodes: RouteNode[], nested: boolean = false): VNode[] => {
|
|
19
|
+
return nodes.map((node, index) => {
|
|
20
|
+
const Component = node.module.Component;
|
|
21
|
+
|
|
22
|
+
// Leaf node - rota final
|
|
23
|
+
if (!node.children) {
|
|
24
|
+
return (
|
|
25
|
+
<Route key={index} path={node.path || ""}>
|
|
26
|
+
<Component />
|
|
27
|
+
</Route>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Node com children - os children são sempre relativos com nest
|
|
32
|
+
const childRoutes = buildRoutes(node.children, !!node.path);
|
|
33
|
+
|
|
34
|
+
// isRoot - pula o componente, renderiza só os children
|
|
35
|
+
if (node.isRoot) {
|
|
36
|
+
return <Switch key={index}>{childRoutes}</Switch>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Sem path - wrapper puro (sem Route)
|
|
40
|
+
if (!node.path) {
|
|
41
|
+
return (
|
|
42
|
+
<Component key={index}>
|
|
43
|
+
<Switch>{childRoutes}</Switch>
|
|
44
|
+
</Component>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Layout com path + nest
|
|
49
|
+
return (
|
|
50
|
+
<Route key={index} path={node.path} nest>
|
|
51
|
+
<Component>
|
|
52
|
+
<Switch>{childRoutes}</Switch>
|
|
53
|
+
</Component>
|
|
54
|
+
</Route>
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// ============================================
|
|
60
|
+
// CLIENT ROUTES - Componente de rotas
|
|
61
|
+
// ============================================
|
|
62
|
+
|
|
63
|
+
const ClientRoutes = ({ routes }: { routes: AppRoutes }) => {
|
|
64
|
+
const routeTree = buildRoutes(routes);
|
|
65
|
+
return <Router>{routeTree}</Router>;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// ============================================
|
|
69
|
+
// START CLIENT - Entry point principal
|
|
70
|
+
// ============================================
|
|
71
|
+
|
|
72
|
+
export const startClient = (options: StartClientOptions) => {
|
|
73
|
+
const { routes } = options;
|
|
74
|
+
const body = document.querySelector("body");
|
|
75
|
+
|
|
76
|
+
if (body) {
|
|
77
|
+
hydrate(<ClientRoutes routes={routes} />, body);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VeloJS Components
|
|
3
|
+
* Components that can be used in the app for script/style injection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Link as WouterLink } from "wouter-preact";
|
|
7
|
+
import type { ComponentChildren } from "preact";
|
|
8
|
+
|
|
9
|
+
// ============================================
|
|
10
|
+
// SCRIPTS COMPONENT
|
|
11
|
+
// ============================================
|
|
12
|
+
|
|
13
|
+
interface ScriptsProps {
|
|
14
|
+
/**
|
|
15
|
+
* Base path for static assets in production
|
|
16
|
+
* @default ""
|
|
17
|
+
*/
|
|
18
|
+
basePath?: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Path to the favicon file relative to the public directory
|
|
22
|
+
* Set to false to disable favicon injection
|
|
23
|
+
* @default "/favicon.ico"
|
|
24
|
+
*/
|
|
25
|
+
favicon?: string | false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Injects the necessary scripts and styles for VeloJS.
|
|
30
|
+
* In dev mode: injects Vite HMR client and velo client script
|
|
31
|
+
* In production: injects compiled CSS and JS
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```tsx
|
|
35
|
+
* <head>
|
|
36
|
+
* <Scripts />
|
|
37
|
+
* </head>
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function Scripts({ basePath, favicon = "/favicon.ico" }: ScriptsProps = {}) {
|
|
41
|
+
const isDev = import.meta.env.DEV;
|
|
42
|
+
basePath = basePath || process.env.STATIC_BASE_URL || "";
|
|
43
|
+
|
|
44
|
+
const faviconTag = favicon !== false && (
|
|
45
|
+
<link rel="icon" href={`${basePath}${favicon}`} type="image/x-icon" />
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (isDev) {
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
{faviconTag}
|
|
52
|
+
<script type="module" src="/@vite/client"></script>
|
|
53
|
+
<script type="module" src="/__velo_client.js"></script>
|
|
54
|
+
</>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<>
|
|
60
|
+
{faviconTag}
|
|
61
|
+
<link rel="stylesheet" href={`${basePath}/client.css`} />
|
|
62
|
+
<script type="module" src={`${basePath}/client.js`}></script>
|
|
63
|
+
</>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============================================
|
|
68
|
+
// LINK COMPONENT
|
|
69
|
+
// ============================================
|
|
70
|
+
|
|
71
|
+
import type { ComponentProps } from "preact";
|
|
72
|
+
import type { RouteModule } from "./types.js";
|
|
73
|
+
|
|
74
|
+
// Props do Link do wouter, mas com "to" estendido
|
|
75
|
+
type WouterLinkProps = ComponentProps<typeof WouterLink>;
|
|
76
|
+
type LinkProps = Omit<WouterLinkProps, "to" | "href"> & {
|
|
77
|
+
/**
|
|
78
|
+
* Destination - can be a string path or a module with metadata
|
|
79
|
+
*/
|
|
80
|
+
to: string | RouteModule;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* URL parameters to substitute in the path
|
|
84
|
+
* e.g., { id: "123" } replaces :id with 123
|
|
85
|
+
*/
|
|
86
|
+
params?: Record<string, string>;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Query string parameters appended to the URL
|
|
90
|
+
* e.g., { company: "abc" } appends ?company=abc
|
|
91
|
+
*/
|
|
92
|
+
search?: Record<string, string> | undefined;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* When true, ignores current URL params and uses fullPath as-is
|
|
96
|
+
* By default, params are extracted from current URL and substituted
|
|
97
|
+
* @default false
|
|
98
|
+
*/
|
|
99
|
+
absolute?: boolean;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Substitutes :param placeholders in a path with actual values
|
|
104
|
+
*/
|
|
105
|
+
export function substituteParams(
|
|
106
|
+
path: string,
|
|
107
|
+
params: Record<string, string>
|
|
108
|
+
): string {
|
|
109
|
+
let result = path;
|
|
110
|
+
for (const [key, value] of Object.entries(params)) {
|
|
111
|
+
result = result.replace(`:${key}`, value);
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Link component for navigation.
|
|
118
|
+
* Accepts either a string path or a route module.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```tsx
|
|
122
|
+
* // With string path
|
|
123
|
+
* <Link to="/login">Login</Link>
|
|
124
|
+
*
|
|
125
|
+
* // With route module (relative - uses path, works with nest)
|
|
126
|
+
* <Link to={McpPage}>MCP</Link>
|
|
127
|
+
*
|
|
128
|
+
* // With route module (absolute - uses fullPath)
|
|
129
|
+
* <Link to={LoginPage} absolute>Login</Link>
|
|
130
|
+
*
|
|
131
|
+
* // With explicit params
|
|
132
|
+
* <Link to={UserPage} params={{ id: "123" }}>View User</Link>
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function Link({ to, params, search, absolute, ...rest }: LinkProps) {
|
|
136
|
+
const isModule = typeof to !== "string";
|
|
137
|
+
|
|
138
|
+
// Default: path (relative), absolute: fullPath
|
|
139
|
+
const basePath = isModule
|
|
140
|
+
? (absolute ? to.metadata?.fullPath : to.metadata?.path) ?? "/"
|
|
141
|
+
: to;
|
|
142
|
+
|
|
143
|
+
// Substitute params if provided
|
|
144
|
+
const finalPath = params ? substituteParams(basePath, params) : basePath;
|
|
145
|
+
|
|
146
|
+
// Absolute module paths: prefix with ~ for wouter absolute navigation
|
|
147
|
+
const routePath = isModule && absolute ? `~${finalPath}` : finalPath;
|
|
148
|
+
|
|
149
|
+
// Append query string if search params provided
|
|
150
|
+
const queryString = search
|
|
151
|
+
? `?${new URLSearchParams(search).toString()}`
|
|
152
|
+
: "";
|
|
153
|
+
|
|
154
|
+
return <WouterLink to={`${routePath}${queryString}`} {...rest} />;
|
|
155
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface VeloConfig {
|
|
2
|
+
/**
|
|
3
|
+
* Diretório base da aplicação onde estão as rotas
|
|
4
|
+
* @default "./app"
|
|
5
|
+
*/
|
|
6
|
+
appDirectory?: string;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Arquivo de rotas com export default AppRoutes
|
|
10
|
+
* @default "routes.tsx"
|
|
11
|
+
*/
|
|
12
|
+
routesFile?: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Arquivo de inicialização do servidor (custom init, sem velojs imports)
|
|
16
|
+
* @default "server.tsx"
|
|
17
|
+
*/
|
|
18
|
+
serverInit?: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Arquivo de inicialização do cliente (custom init, sem velojs imports)
|
|
22
|
+
* @default "client.tsx"
|
|
23
|
+
*/
|
|
24
|
+
clientInit?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function defineConfig(config: VeloConfig): VeloConfig {
|
|
28
|
+
return config;
|
|
29
|
+
}
|
package/src/cookie.ts
ADDED
package/src/factory.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createMiddleware, createFactory } from "hono/factory";
|