@mauroandre/velojs 0.0.59 → 0.0.60
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 +54 -1
- package/agent/AGENTS.md +21 -0
- package/agent/skills/velojs-type-reference/SKILL.md +1 -0
- package/agent/skills/velojs-vite-plugin/SKILL.md +51 -2
- package/dist/config.d.ts +8 -0
- package/dist/leak-diagnostic.d.ts +77 -0
- package/dist/leak-diagnostic.js +14 -0
- package/dist/vite.d.ts +36 -0
- package/dist/vite.js +811 -410
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -170,6 +170,7 @@ veloPlugin({
|
|
|
170
170
|
serverInit: "server.tsx", // default
|
|
171
171
|
clientInit: "client.tsx", // default
|
|
172
172
|
hostname: "127.0.0.1", // bind interface, dev and production (the HOST env wins)
|
|
173
|
+
serverOnly: ["server/**"], // declares server-only modules to the leak diagnostic
|
|
173
174
|
});
|
|
174
175
|
```
|
|
175
176
|
|
|
@@ -1780,13 +1781,15 @@ Callbacks queue until the server starts. If called after startup, executes immed
|
|
|
1780
1781
|
|
|
1781
1782
|
## Vite Plugin Architecture
|
|
1782
1783
|
|
|
1783
|
-
`veloPlugin()` returns
|
|
1784
|
+
`veloPlugin()` returns 8 plugins:
|
|
1784
1785
|
|
|
1785
1786
|
| Plugin | Purpose |
|
|
1786
1787
|
|--------|---------|
|
|
1787
1788
|
| `velo:config` | Build config (client/server modes, aliases, defines) |
|
|
1788
1789
|
| `velo:transform` | AST transforms (metadata injection, action stubs, loader removal) |
|
|
1789
1790
|
| `velo:static-url` | Rewrites CSS `url(/path)` to `url(STATIC_BASE_URL/path)` at build time |
|
|
1791
|
+
| `velo:graph` | Generates `.velojs/graph.json` (route tree + dependency graph) on build and dev |
|
|
1792
|
+
| `velo:leak-diagnostic` | Prints the server→client leak report on build and dev (read-only) |
|
|
1790
1793
|
| `@preact/preset-vite` | Preact JSX support |
|
|
1791
1794
|
| `@hono/vite-dev-server` | Dev server with SSR |
|
|
1792
1795
|
| `velo:ws-bridge` | Exposes Vite's HTTP server for WebSocket handlers in dev mode |
|
|
@@ -1829,6 +1832,55 @@ velojs build
|
|
|
1829
1832
|
|
|
1830
1833
|
When `routes.tsx` changes, the plugin rebuilds the fullPath map and triggers a full page reload (not partial HMR).
|
|
1831
1834
|
|
|
1835
|
+
### Server→Client Leak Diagnostic
|
|
1836
|
+
|
|
1837
|
+
What ships to the browser is checked on every `velojs build` (including `build --static`) and `velojs dev`: the terminal prints a report of every server module the client entry can reach — a leak of the server→client boundary.
|
|
1838
|
+
|
|
1839
|
+
A **server module** is a module that touches a Node builtin in its own code — static import, re-export or `await import()`, by any import path — or that matches a `serverOnly` pattern declared in the plugin config.
|
|
1840
|
+
A **leak** is a server module reachable from what the browser loads, following static and dynamic imports.
|
|
1841
|
+
The analysis reads the code *after* the client transforms — the same ones the build applies (loaders, actions, streams, sockets, middlewares and endpoints stripped, orphaned imports pruned) — so the normal VeloJS shape (a `loader` importing server code) is silent by construction: what the framework removes never travels.
|
|
1842
|
+
|
|
1843
|
+
Each leak in the report brings:
|
|
1844
|
+
|
|
1845
|
+
- the leaked module and what qualifies it as server (the Node builtin touched, or the `serverOnly` pattern matched);
|
|
1846
|
+
- the full import chain from the client entry to it — file and line at every link;
|
|
1847
|
+
- the **entry point** — the import where the chain pulls the first server module, marked `entry point (cut here)`: exactly where the cut happens.
|
|
1848
|
+
|
|
1849
|
+
```text
|
|
1850
|
+
[velojs] server->client leak analysis: 1 leak(s) in what ships to the client.
|
|
1851
|
+
|
|
1852
|
+
1) app/server-side.ts
|
|
1853
|
+
server because: imports Node builtin "node:fs"
|
|
1854
|
+
import chain from the client entry:
|
|
1855
|
+
virtual:velo/client-entry (generated) -> app/routes.tsx
|
|
1856
|
+
app/routes.tsx:3 -> app/pages/Home.tsx
|
|
1857
|
+
app/pages/Home.tsx:6 -> app/server-side.ts <- entry point (cut here)
|
|
1858
|
+
|
|
1859
|
+
The report is read-only: nothing is blocked or rewritten; the cut is yours.
|
|
1860
|
+
```
|
|
1861
|
+
|
|
1862
|
+
Two chains to the same module are two items — every cut point shows up — and the list is stable between runs.
|
|
1863
|
+
When there is nothing to report, one line says so (`0 leaks — what ships to the client is clean.`), so silence is distinguishable from the analysis not running.
|
|
1864
|
+
In dev the report appears when the server comes up (no navigation needed) and is re-emitted whenever a file change shifts what travels to the client — an import added stitches a new path into the next report, an import removed takes the leak out of it.
|
|
1865
|
+
|
|
1866
|
+
The report is the light, never the hand: nothing is blocked, rewritten, stubbed or moved, `await import()` included; the build finishes with success, the dev keeps serving, and the decision to cut any edge is entirely yours.
|
|
1867
|
+
|
|
1868
|
+
#### Declaring server-only code: `serverOnly`
|
|
1869
|
+
|
|
1870
|
+
A module that touches no Node builtin is invisible to the analysis — the framework does not guess your app's domain.
|
|
1871
|
+
Declare it:
|
|
1872
|
+
|
|
1873
|
+
```typescript
|
|
1874
|
+
veloPlugin({
|
|
1875
|
+
serverOnly: ["src/fsm/**", "server/**"],
|
|
1876
|
+
});
|
|
1877
|
+
```
|
|
1878
|
+
|
|
1879
|
+
Patterns are globs (`*`, `**`, `?`) matched against the module's file path relative to the project root, always with `/` as separator.
|
|
1880
|
+
The declaration feeds the diagnostic only — nothing is blocked or altered because of it.
|
|
1881
|
+
|
|
1882
|
+
**Limitation (craved):** server code that touches no Node builtin and matches no `serverOnly` pattern generates no item; that is what the declaration is for.
|
|
1883
|
+
|
|
1832
1884
|
---
|
|
1833
1885
|
|
|
1834
1886
|
## Request Isolation
|
|
@@ -1986,6 +2038,7 @@ interface VeloConfig {
|
|
|
1986
2038
|
clientInit?: string; // default: "client.tsx"
|
|
1987
2039
|
port?: number; // default: 3000; the PORT env wins
|
|
1988
2040
|
hostname?: string; // bind interface, dev and production; the HOST env wins
|
|
2041
|
+
serverOnly?: string[]; // glob patterns declaring server-only modules to the leak diagnostic
|
|
1989
2042
|
}
|
|
1990
2043
|
```
|
|
1991
2044
|
|
package/agent/AGENTS.md
CHANGED
|
@@ -39,6 +39,27 @@ hard constraints, not style.
|
|
|
39
39
|
|
|
40
40
|
Never pass a module id to `useLoader()` / `Loader()` yourself — the plugin injects it.
|
|
41
41
|
|
|
42
|
+
## The server→client leak report
|
|
43
|
+
|
|
44
|
+
`velojs build` (and `build --static`, `velojs dev`) prints a report of every **server module** the browser can reach — a module that touches a Node builtin in its own code (static import, re-export or `await import()`), or that matches a `serverOnly` pattern.
|
|
45
|
+
The analysis reads the code **after** the client transforms (the table above describes what those remove), so a `loader`/`action_*`/`stream_*`/`socket_*`/`middlewares` importing server code where the framework strips it is silent by construction.
|
|
46
|
+
|
|
47
|
+
What the warning means: the module named in the item reaches what ships to the browser; the chain lists every import from the client entry to it (file and line per link); `entry point (cut here)` marks the import where the chain pulls the first server module — that is where the cut happens.
|
|
48
|
+
The report never blocks, rewrites, stubs or moves anything: the build finishes with success, the dev keeps serving, and cutting any edge is always your call.
|
|
49
|
+
Two chains to the same module are two items — every cut point shows up.
|
|
50
|
+
|
|
51
|
+
Code that touches **no** Node builtin is invisible to the analysis — the framework does not guess your app's domain. Declare it:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
veloPlugin({
|
|
55
|
+
serverOnly: ["src/fsm/**", "server/**"],
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Patterns are globs (`*`, `**`, `?`) matched against the module's file path relative to the project root, always with `/` as separator.
|
|
60
|
+
The declaration feeds the diagnostic only — nothing is blocked or altered because of it.
|
|
61
|
+
A server module with no builtin and no declaration generates no item: that limitation is craved, and the declaration is how you lift it.
|
|
62
|
+
|
|
42
63
|
## Sharing a layout's data with its children
|
|
43
64
|
|
|
44
65
|
`Loader()` is the importable handle for a module's loader data; `useLoader()` is the
|
|
@@ -139,5 +139,6 @@ interface VeloConfig {
|
|
|
139
139
|
clientInit?: string; // default: "client.tsx"
|
|
140
140
|
port?: number; // default: 3000 (the PORT env always wins)
|
|
141
141
|
hostname?: string; // bind interface, dev and production; the HOST env wins
|
|
142
|
+
serverOnly?: string[]; // glob patterns declaring server-only modules to the leak diagnostic
|
|
142
143
|
}
|
|
143
144
|
```
|
|
@@ -5,9 +5,9 @@ description: "The `veloPlugin` build pipeline: the 9 AST transforms that inject
|
|
|
5
5
|
|
|
6
6
|
# Vite Plugin
|
|
7
7
|
|
|
8
|
-
The `veloPlugin()` is the core of VeloJS. It's a single function that returns
|
|
8
|
+
The `veloPlugin()` is the core of VeloJS. It's a single function that returns 8 Vite plugins working together to handle routing, SSR, code transforms, and more.
|
|
9
9
|
|
|
10
|
-
## The
|
|
10
|
+
## The 8 plugins
|
|
11
11
|
|
|
12
12
|
| Plugin | What it does |
|
|
13
13
|
|--------|-------------|
|
|
@@ -15,6 +15,7 @@ The `veloPlugin()` is the core of VeloJS. It's a single function that returns 6
|
|
|
15
15
|
| `velo:transform` | Performs AST transforms on your code (the most important plugin) |
|
|
16
16
|
| `velo:static-url` | Rewrites CSS `url(/path)` to use `STATIC_BASE_URL` in production builds |
|
|
17
17
|
| `velo:graph` | Generates `.velojs/graph.json` (route tree + dependency graph) on build and dev |
|
|
18
|
+
| `velo:leak-diagnostic` | Prints the server→client leak report on build and dev (read-only) |
|
|
18
19
|
| `@preact/preset-vite` | Adds Preact JSX support |
|
|
19
20
|
| `@hono/vite-dev-server` | Runs the SSR dev server |
|
|
20
21
|
| `velo:ws-bridge` | Exposes Vite's HTTP server for WebSocket handlers during development |
|
|
@@ -79,6 +80,54 @@ VeloJS uses Vite virtual modules to generate the server and client entry points
|
|
|
79
80
|
|
|
80
81
|
You never import these directly — they're generated by the plugin.
|
|
81
82
|
|
|
83
|
+
## The server→client leak diagnostic
|
|
84
|
+
|
|
85
|
+
What ships to the browser is checked on every `velojs build` (including `build --static`) and `velojs dev`: the terminal prints a report of every server module the client entry can reach — a leak of the server→client boundary.
|
|
86
|
+
|
|
87
|
+
A **server module** is a module that touches a Node builtin in its own code — static import, re-export or `await import()`, by any import path — or that matches a `serverOnly` pattern declared in the plugin config.
|
|
88
|
+
A **leak** is a server module reachable from what the browser loads, following static and dynamic imports.
|
|
89
|
+
The analysis reads the code *after* the client transforms above — loaders, actions, streams, sockets, middlewares and endpoints stripped, orphaned imports pruned — never the raw source, so a `loader` importing server code is silent by construction.
|
|
90
|
+
|
|
91
|
+
Each leak in the report brings:
|
|
92
|
+
|
|
93
|
+
- the leaked module and what qualifies it as server (the Node builtin touched, or the `serverOnly` pattern matched);
|
|
94
|
+
- the full import chain from the client entry to it — file and line at every link;
|
|
95
|
+
- the **entry point** — the import where the chain pulls the first server module, marked `entry point (cut here)`: exactly where the cut happens.
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
[velojs] server->client leak analysis: 1 leak(s) in what ships to the client.
|
|
99
|
+
|
|
100
|
+
1) app/server-side.ts
|
|
101
|
+
server because: imports Node builtin "node:fs"
|
|
102
|
+
import chain from the client entry:
|
|
103
|
+
virtual:velo/client-entry (generated) -> app/routes.tsx
|
|
104
|
+
app/routes.tsx:3 -> app/pages/Home.tsx
|
|
105
|
+
app/pages/Home.tsx:6 -> app/server-side.ts <- entry point (cut here)
|
|
106
|
+
|
|
107
|
+
The report is read-only: nothing is blocked or rewritten; the cut is yours.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Two chains to the same module are two items — every cut point shows up — and the list is stable between runs.
|
|
111
|
+
When nothing leaks, one line says so (`0 leaks — what ships to the client is clean.`), so silence is distinguishable from the analysis not running.
|
|
112
|
+
In dev the report appears when the server comes up (no navigation needed) and is re-emitted whenever a file change shifts what travels to the client.
|
|
113
|
+
|
|
114
|
+
### Declaring server-only code: `serverOnly`
|
|
115
|
+
|
|
116
|
+
A module that touches no Node builtin is invisible to the analysis — the framework does not guess your app's domain.
|
|
117
|
+
Declare it:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
veloPlugin({
|
|
121
|
+
serverOnly: ["src/fsm/**", "server/**"],
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Patterns are globs (`*`, `**`, `?`) matched against the module's file path relative to the project root, always with `/` as separator.
|
|
126
|
+
The declaration feeds the diagnostic only — nothing is blocked or altered because of it.
|
|
127
|
+
|
|
128
|
+
**Limitation (craved):** server code that touches no Node builtin and matches no `serverOnly` pattern generates no item; that is what the declaration is for.
|
|
129
|
+
The diagnostic is the light, never the hand: nothing is blocked, rewritten, stubbed or moved, `await import()` included; the build finishes with success and the dev keeps serving.
|
|
130
|
+
|
|
82
131
|
## Hot reload
|
|
83
132
|
|
|
84
133
|
When you edit `routes.tsx`, the plugin detects the change, rebuilds the path map, and triggers a full page reload. Other file changes use Vite's standard HMR (hot module replacement).
|
package/dist/config.d.ts
CHANGED
|
@@ -40,5 +40,13 @@ export interface VeloConfig {
|
|
|
40
40
|
* @hono/vite-dev-server, não os substituem.
|
|
41
41
|
*/
|
|
42
42
|
devServerExclude?: (string | RegExp)[];
|
|
43
|
+
/**
|
|
44
|
+
* Padrões de glob (`*`, `**`, `?`) que declaram módulos só de servidor ao
|
|
45
|
+
* diagnóstico de vazamento server->client, casados contra o caminho do
|
|
46
|
+
* arquivo do módulo relativo à raiz do projeto, com `/` como separador
|
|
47
|
+
* (ex.: `serverOnly: ["src/fsm/**", "server/**"]`). Serve só ao
|
|
48
|
+
* diagnóstico — nada é bloqueado ou alterado por causa da declaração.
|
|
49
|
+
*/
|
|
50
|
+
serverOnly?: string[];
|
|
43
51
|
}
|
|
44
52
|
export declare function defineConfig(config: VeloConfig): VeloConfig;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** One import in the chain: `from` imports `to` at `line` (`null` = generated). */
|
|
2
|
+
export interface LeakLink {
|
|
3
|
+
from: string;
|
|
4
|
+
line: number | null;
|
|
5
|
+
to: string;
|
|
6
|
+
}
|
|
7
|
+
export interface LeakItem {
|
|
8
|
+
/** Display path of the leaked server module. */
|
|
9
|
+
target: string;
|
|
10
|
+
/** Why it qualifies as a server module (English). */
|
|
11
|
+
qualifiers: string[];
|
|
12
|
+
/** Full import chain, client entry -> target, one link per import. */
|
|
13
|
+
chain: LeakLink[];
|
|
14
|
+
/** Index in `chain` of the entry-point link — the cut. */
|
|
15
|
+
entryIndex: number;
|
|
16
|
+
}
|
|
17
|
+
export interface LeakReport {
|
|
18
|
+
leaks: LeakItem[];
|
|
19
|
+
/** True when the enumeration stopped at its safety budget. */
|
|
20
|
+
truncated: boolean;
|
|
21
|
+
/** Stable fingerprint of what ships to the client (dev re-emit trigger). */
|
|
22
|
+
signature: string;
|
|
23
|
+
/** How many modules the walk visited (informational). */
|
|
24
|
+
moduleCount: number;
|
|
25
|
+
}
|
|
26
|
+
export type ResolveFn = (spec: string, importer: string) => string | null;
|
|
27
|
+
export interface ModuleInfo {
|
|
28
|
+
file: string;
|
|
29
|
+
display: string;
|
|
30
|
+
edges: RawEdge[];
|
|
31
|
+
builtins: string[];
|
|
32
|
+
patterns: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface CachedModule {
|
|
35
|
+
mtimeMs: number;
|
|
36
|
+
info: ModuleInfo;
|
|
37
|
+
}
|
|
38
|
+
export interface LeakAnalyzerOptions {
|
|
39
|
+
/** Project root — display paths and `serverOnly` matching are relative to it. */
|
|
40
|
+
rootDir: string;
|
|
41
|
+
/** Absolute app directory (velo:transform's scope). */
|
|
42
|
+
appDir: string;
|
|
43
|
+
/** Routes file name inside appDir (default "routes.tsx"). */
|
|
44
|
+
routesFile?: string | undefined;
|
|
45
|
+
/** The client entry's import specifiers (the virtual entry's imports). */
|
|
46
|
+
entrySpecs: string[];
|
|
47
|
+
/** Glob patterns declaring server-only modules (VeloConfig.serverOnly). */
|
|
48
|
+
serverOnly?: readonly string[] | undefined;
|
|
49
|
+
/** Import resolver: (specifier, importer file) -> absolute file or null. */
|
|
50
|
+
resolve?: ResolveFn | undefined;
|
|
51
|
+
/** Optional parse cache shared across runs (dev). */
|
|
52
|
+
cache?: Map<string, CachedModule> | undefined;
|
|
53
|
+
}
|
|
54
|
+
interface RawEdge {
|
|
55
|
+
spec: string;
|
|
56
|
+
line: number;
|
|
57
|
+
kind: "static" | "dynamic";
|
|
58
|
+
locals: string[];
|
|
59
|
+
}
|
|
60
|
+
/** True when the specifier names a Node builtin (with or without the `node:` prefix). */
|
|
61
|
+
export declare function isBuiltinSpec(spec: string): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Compiles a glob (`*` = within a segment, `**` across segments, `?` = one
|
|
64
|
+
* char) into a full-match regex over the module path relative to the project
|
|
65
|
+
* root, always with `/` separators.
|
|
66
|
+
*/
|
|
67
|
+
export declare function globToRegex(glob: string): RegExp;
|
|
68
|
+
/**
|
|
69
|
+
* Resolution mirroring what the client build follows: relative specifiers with
|
|
70
|
+
* TS extension completion, bare specifiers through `node_modules` with the
|
|
71
|
+
* package's browser/import `exports` conditions. Returns null for anything
|
|
72
|
+
* that does not resolve to a file (external, virtual or unresolvable).
|
|
73
|
+
*/
|
|
74
|
+
export declare function defaultResolve(spec: string, importer: string): string | null;
|
|
75
|
+
export declare function analyzeClientLeaks(options: LeakAnalyzerOptions): LeakReport;
|
|
76
|
+
export declare function formatLeakReport(report: LeakReport): string;
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import "node:fs";
|
|
2
|
+
import "node:path";
|
|
3
|
+
import "node:module";
|
|
4
|
+
import "@babel/parser";
|
|
5
|
+
import "@babel/traverse";
|
|
6
|
+
import "@babel/types";
|
|
7
|
+
import { a as s, d as l, f, g, i as n } from "./vite.js";
|
|
8
|
+
export {
|
|
9
|
+
s as analyzeClientLeaks,
|
|
10
|
+
l as defaultResolve,
|
|
11
|
+
f as formatLeakReport,
|
|
12
|
+
g as globToRegex,
|
|
13
|
+
n as isBuiltinSpec
|
|
14
|
+
};
|
package/dist/vite.d.ts
CHANGED
|
@@ -81,6 +81,42 @@ export declare function declaredChannelNames(code: string): string[] | null;
|
|
|
81
81
|
* A missing file yields an empty set, which makes every declaration unknown.
|
|
82
82
|
*/
|
|
83
83
|
export declare function readChannelMapNames(file: string): Set<string>;
|
|
84
|
+
export interface ModuleTransformOptions {
|
|
85
|
+
/** Absolute module id (the Vite module id). */
|
|
86
|
+
id: string;
|
|
87
|
+
/** Absolute app directory (velo:transform's scope). */
|
|
88
|
+
appDir: string;
|
|
89
|
+
/** Whether this is the SSR/server pass. */
|
|
90
|
+
isSSR: boolean;
|
|
91
|
+
/** Routes file name inside appDir (default "routes.tsx"). */
|
|
92
|
+
routesFile?: string | undefined;
|
|
93
|
+
/** Route path info (metadata injection only — never changes imports). */
|
|
94
|
+
pathInfo?: PathInfo | undefined;
|
|
95
|
+
}
|
|
96
|
+
export interface ModuleTransformResult {
|
|
97
|
+
code: string;
|
|
98
|
+
moduleId: string;
|
|
99
|
+
}
|
|
100
|
+
/** The moduleId velo:transform derives for a module (appDir-relative, no ext). */
|
|
101
|
+
export declare function moduleIdFor(appDir: string, id: string): string;
|
|
102
|
+
/**
|
|
103
|
+
* The exact module transform velo:transform applies — one pipeline, two users:
|
|
104
|
+
* the build itself and the server->client leak diagnostic (which must see what
|
|
105
|
+
* travels, never a reprint that could diverge). Returns null when the module is
|
|
106
|
+
* out of scope or no transformation fired; the build then ships the source
|
|
107
|
+
* untouched.
|
|
108
|
+
*/
|
|
109
|
+
export declare function transformModuleCode(code: string, options: ModuleTransformOptions): ModuleTransformResult | null;
|
|
110
|
+
/**
|
|
111
|
+
* The client entry's import specifiers — the same list the generated
|
|
112
|
+
* `virtual:velo/client-entry` module imports. Shared so the leak diagnostic
|
|
113
|
+
* starts its walk at exactly what the browser loads.
|
|
114
|
+
*/
|
|
115
|
+
export declare function clientEntryModulePaths(appDir: string, veloConfig: VeloConfig): {
|
|
116
|
+
clientInit: string;
|
|
117
|
+
routes: string;
|
|
118
|
+
framework: string;
|
|
119
|
+
};
|
|
84
120
|
/**
|
|
85
121
|
* @hono/vite-dev-server treats `exclude` as all-or-nothing: passing one
|
|
86
122
|
* replaces the defaults entirely. We compose instead — the upstream defaults
|