vaderjs-native 1.0.27 β†’ 1.0.30

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 CHANGED
@@ -1,18 +1,7 @@
1
- ## <p align="center">
2
-
3
- <a href="[https://vader-js.pages.dev](https://vader-js.pages.dev)">
4
- <picture>
5
- <source media="(prefers-color-scheme: dark)" srcset="/icon.jpeg">
6
- <img src="[https://github.com/Postr-Inc/Vader.js/blob/main/logo.png](https://github.com/Postr-Inc/Vader.js/blob/main/logo.png)" height="128">
7
- </picture>
8
- <h1 align="center">VaderNative</h1>
9
- </a>
10
- </p>
1
+ # VaderNative
11
2
 
12
3
  **VaderNative** is a high-performance, reactive framework for building truly native cross-platform applications. It combines a familiar React-like developer experience with a "Native-First" philosophyβ€”streaming logs to your terminal, bundling single-file executables, and maintaining a zero-Virtual-DOM overhead.
13
4
 
14
- ---
15
-
16
5
  ## πŸ›  Developer Environment Setup
17
6
 
18
7
  Before you start coding, ensure your machine is equipped for native compilation.
@@ -33,11 +22,8 @@ To build and run on Android, you need the **Android SDK**:
33
22
  # Add to your .bashrc, .zshrc, or Windows ENV
34
23
  ANDROID_HOME=$HOME/Android/Sdk
35
24
  PATH=$PATH:$ANDROID_HOME/platform-tools
36
-
37
25
  ```
38
26
 
39
-
40
-
41
27
  ### 3. Windows Setup (Desktop)
42
28
 
43
29
  To build **WinUI 3** native desktop apps:
@@ -46,47 +32,81 @@ To build **WinUI 3** native desktop apps:
46
32
  * **.NET 8 SDK:** [Download here](https://dotnet.microsoft.com/download/dotnet/8.0).
47
33
  * **Windows App SDK:** Managed automatically by the VaderNative build script.
48
34
 
49
- ---
50
-
51
35
  ## πŸš€ Getting Started
52
36
 
53
37
  ### 1. Installation
54
38
 
55
39
  ```bash
56
40
  bun install vaderjs-native@latest
57
-
58
41
  ```
59
42
 
60
43
  ### 2. Create your first page
61
44
 
62
- VaderNative uses **File-Based Routing**. Create a file at `app/index.jsx`:
63
-
64
45
  ```tsx
65
- import * as Vader from "vader-native";
46
+ // App.tsx in root folder
47
+ import * as Vader from "vaderjs-native";
48
+ import { useRoute } from "vaderjs-native/router";
49
+ import { router } from "./src/router";
66
50
 
67
- export default function App() {
68
- const [count, setCount] = Vader.useState(0);
51
+ function App() {
52
+ const route = useRoute();
69
53
 
70
- return (
71
- <div style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
72
- <p style={{ fontSize: 24 }}>Count: {count}</p>
73
- <button title="Increment" onPress={() => setCount(count + 1)} />
74
- </div>
75
- );
54
+ if (!route) {
55
+ const Fallback = router.getFallback();
56
+ return <Fallback />;
57
+ }
58
+
59
+ const Component = route.route.component;
60
+ return <Component params={route.params} />;
76
61
  }
77
62
 
63
+ Vader.render(<App />, document.getElementById("app")!);
78
64
  ```
79
65
 
80
- ---
66
+ ```tsx
67
+ // src/router.tsx
68
+ import * as Vader from "vaderjs-native";
69
+ import { createRouter } from "vaderjs-native/router";
70
+ import Home from "../app/index";
71
+ import Login from "../app/login";
72
+ export const router = createRouter({
73
+ mode: "history",
74
+ routes: [
75
+ { path: "/", component: Home },
76
+ { path: "/login", component: Login },
77
+ { path: "/login/", component: Login },
78
+ ],
79
+ fallback: function NotFound() {
80
+ return <div>404 - Page Not Found</div>;
81
+ },
82
+ });
83
+ ```
81
84
 
82
- ## βš™οΈ Configuration (`vader.config.ts`)
85
+ ```tsx
86
+ // app/index.tsx
87
+ import * as Vader from "vaderjs-native";
88
+ import { router } from "../src/router";
89
+
90
+ export default function Home() {
91
+ Vader.useEffect(() => {
92
+ (async () => {
93
+ const token = await Vader.secureStore.get("auth_token");
94
+ if (!token) {
95
+ router.navigate("/login");
96
+ }
97
+ })();
98
+ }, []);
99
+
100
+ return <div>Hello World</div>;
101
+ }
102
+ ```
83
103
 
84
- Control your app's DNA from a single config file:
104
+ ## βš™οΈ Configuration (`vaderjs.config.js`)
85
105
 
86
- ```ts
87
- import defineConfig from "vaderjs-native/config";
106
+ Control your app's DNA from a single config file:
88
107
 
89
- export default defineConfig({
108
+ ```javascript
109
+ export default {
90
110
  app: {
91
111
  name: "MoviesPlus",
92
112
  id: "com.moviesplus.app",
@@ -104,12 +124,9 @@ export default defineConfig({
104
124
  icon: "./public/windows/icon.ico",
105
125
  }
106
126
  }
107
- });
108
-
127
+ };
109
128
  ```
110
129
 
111
- ---
112
-
113
130
  ## πŸ’» CLI Commands & Workflow
114
131
 
115
132
  VaderNative is designed for a **Terminal-First** workflow. No need to keep native IDEs (Android Studio/Visual Studio) open for debugging.
@@ -119,12 +136,14 @@ VaderNative is designed for a **Terminal-First** workflow. No need to keep nativ
119
136
  Automatically syncs assets, starts the dev server, and streams native logs to your console.
120
137
 
121
138
  ```bash
122
- # Run Windows Dev (Streams app.log to terminal)
139
+ # Web Development (SPA)
140
+ bun run vaderjs dev
141
+
142
+ # Windows Dev (Streams app.log to terminal)
123
143
  bun run vaderjs windows:dev
124
144
 
125
- # Run Android Dev
145
+ # Android Dev
126
146
  bun run vaderjs android:dev
127
-
128
147
  ```
129
148
 
130
149
  ### Production Building
@@ -132,15 +151,34 @@ bun run vaderjs android:dev
132
151
  Compile your app into a distributable format.
133
152
 
134
153
  ```bash
154
+ # Build for web
155
+ bun run vaderjs build
156
+
135
157
  # Create a Single-File Windows EXE (/release/App.exe)
136
158
  bun run vaderjs windows:build
137
159
 
138
160
  # Build Android APK/Bundle
139
- bun run vaderjs android:build
161
+ bun run vaderjs android:build
140
162
 
163
+ # Serve production build
164
+ bun run vaderjs serve
141
165
  ```
142
166
 
143
- ---
167
+ ### Project Management
168
+
169
+ ```bash
170
+ # Create a new Vader project
171
+ bun run vaderjs init [project-name]
172
+
173
+ # Add a Vader plugin
174
+ bun run vaderjs add <plugin-name>
175
+
176
+ # Remove a Vader plugin
177
+ bun run vaderjs remove <plugin-name>
178
+
179
+ # List installed plugins
180
+ bun run vaderjs list_plugins
181
+ ```
144
182
 
145
183
  ## πŸͺ΅ Native Logging Strategy
146
184
 
@@ -148,22 +186,26 @@ VaderNative implements **Native Pipe & Log Tailing**.
148
186
  * **Windows:** The CLI tails `app.log` using a shared-access stream, ensuring you see crashes even if the app UI freezes.
149
187
  * **Android:** The CLI automatically filters `logcat` to show only your app's specific tags.
150
188
 
151
- ---
152
-
153
189
  ## πŸ—‚ Project Structure
154
190
 
155
191
  | Directory | Description |
156
192
  | --- | --- |
157
- | `app/` | **Routes:** File-based routing (e.g., `index.jsx`, `settings.jsx`). |
158
- | `src/` | **Logic:** Shared components, hooks, and business logic. |
159
- | `public/` | **Assets:** Images, fonts, and static data. |
160
- | `build/` | **Generated:** The native source code (WinUI/Android project files). |
161
-
162
- ---
193
+ | `App.tsx` | **Root App Component:** Main entry point for your SPA/MPA |
194
+ | `src/` | **Logic:** Shared components, hooks, and business logic |
195
+ | `public/` | **Assets:** Images, fonts, and static data |
196
+ | `dist/` | **Build Output:** Generated web assets |
197
+ | `build/` | **Native Projects:** Generated native source code (WinUI/Android project files) |
163
198
 
164
199
  ## ✨ Why VaderNative?
165
200
 
166
- * **Native Speed:** No heavy Virtual DOM; updates are sent directly to native views.
167
- * **Single-File Windows Apps:** No complex installers; just one `.exe`.
168
- * **Bun-First:** Leverages the fastest JS runtime for building and bundling.
169
- * **Modern Tooling:** Tail logs, auto-patch `.csproj`, and hot-reload from one terminal.
201
+ * **Native Speed:** No heavy Virtual DOM; updates are sent directly to native views
202
+ * **Single-File Windows Apps:** No complex installers; just one `.exe`
203
+ * **Bun-First:** Leverages the fastest JS runtime for building and bundling
204
+ * **Modern Tooling:** Tail logs, auto-patch `.csproj`, and hot-reload from one terminal
205
+ * **File-Based Routing:** Automatic route generation from `src/pages/` or `src/routes/`
206
+ * **SPA/MPA Support:** Choose between Single Page or Multi Page Application architecture
207
+ * **Plugin System:** Extend functionality with community plugins
208
+ * **Hot Module Replacement:** Fast development with real-time updates
209
+ * **Cross-Platform:** Build for web, Android, and Windows from the same codebase
210
+
211
+
@@ -0,0 +1,384 @@
1
+ import path from "path";
2
+ import fsSync from "fs";
3
+ import { serve, FileSystemRouter, glob } from "bun";
4
+ import { logger } from "vaderjs-native/cli/logger.js";
5
+ import { buildAll } from "vaderjs-native/main.js";
6
+
7
+ const PROJECT_ROOT = path.join(process.cwd());
8
+ const DIST_DIR = path.join(PROJECT_ROOT, "dist");
9
+
10
+ // Helper to check if a path should be ignored
11
+ function shouldIgnorePath(filePath: string): boolean {
12
+ const normalized = path.normalize(filePath);
13
+
14
+ // Ignore common system and build directories
15
+ const ignorePatterns = [
16
+ path.normalize('node_modules'),
17
+ path.normalize('dist'),
18
+ path.normalize('.git'),
19
+ path.normalize('.vscode'),
20
+ path.normalize('.idea'),
21
+ path.normalize('build'),
22
+ path.normalize('.next'),
23
+ path.normalize('.nuxt'),
24
+ path.normalize('.cache'),
25
+ path.normalize('temp'),
26
+ path.normalize('tmp'),
27
+ path.normalize('coverage'),
28
+ path.normalize('.npm'),
29
+ path.normalize('yarn.lock'),
30
+ path.normalize('package-lock.json'),
31
+ path.normalize('.env'),
32
+ ];
33
+
34
+ // Also ignore common temporary and system files
35
+ const ignoreFilePatterns = [
36
+ /\.log$/,
37
+ /\.tmp$/,
38
+ /\.temp$/,
39
+ /\.swp$/,
40
+ /~$/,
41
+ /\.DS_Store$/,
42
+ /Thumbs\.db$/,
43
+ /desktop\.ini$/,
44
+ ];
45
+
46
+ // Check if path contains any ignore patterns
47
+ for (const pattern of ignorePatterns) {
48
+ if (normalized.includes(pattern)) {
49
+ return true;
50
+ }
51
+ }
52
+
53
+ // Check if filename matches ignore patterns
54
+ const filename = path.basename(normalized);
55
+ for (const pattern of ignoreFilePatterns) {
56
+ if (pattern.test(filename)) {
57
+ return true;
58
+ }
59
+ }
60
+
61
+ return false;
62
+ }
63
+
64
+ // Track watchers for cleanup
65
+ const watchers = new Map<string, any>();
66
+
67
+ function safeWatch(dir: string, cb: (event: string, filename: string | null) => void) {
68
+ // Don't watch system directories or paths outside project
69
+ if (shouldIgnorePath(dir) || !dir.startsWith(PROJECT_ROOT)) {
70
+ logger.debug(`Skipping watch on directory: ${dir}`);
71
+ return null;
72
+ }
73
+
74
+ // Check if directory exists
75
+ if (!fsSync.existsSync(dir)) {
76
+ logger.warn(`Directory does not exist: ${dir}`);
77
+ return null;
78
+ }
79
+
80
+ try {
81
+ // Close existing watcher if any
82
+ if (watchers.has(dir)) {
83
+ try {
84
+ watchers.get(dir).close();
85
+ } catch (err) {
86
+ // Ignore close errors
87
+ }
88
+ }
89
+
90
+ const watcher = fsSync.watch(dir, { recursive: true }, (event: string, filename: string | null) => {
91
+ if (!filename) return;
92
+
93
+ const changedFile = path.join(dir, filename);
94
+
95
+ // Skip ignored files
96
+ if (shouldIgnorePath(changedFile)) {
97
+ return;
98
+ }
99
+
100
+ // Only process files we care about
101
+ const ext = path.extname(changedFile).toLowerCase();
102
+ const relevantExtensions = ['.ts', '.tsx', '.js', '.jsx', '.css', '.html', '.json', '.config.js', '.config.ts'];
103
+
104
+ if (relevantExtensions.includes(ext) || ext === '') {
105
+ logger.info(`πŸ“ File changed: ${path.relative(PROJECT_ROOT, changedFile)}`);
106
+ cb(event, filename);
107
+ }
108
+ });
109
+
110
+ watcher.on("error", (err: Error) => logger.warn(`Watcher error on ${dir}:`, err.message));
111
+ watchers.set(dir, watcher);
112
+
113
+ logger.info(`πŸ‘€ Watching directory: ${path.relative(PROJECT_ROOT, dir)}`);
114
+ return watcher;
115
+ } catch (err: any) {
116
+ logger.warn(`Failed to watch ${dir}:`, err.message);
117
+ return null;
118
+ }
119
+ }
120
+
121
+ async function loadConfig() {
122
+ try {
123
+ const configModule = await import(path.join(PROJECT_ROOT, "vaderjs.config.js"));
124
+ return configModule.default || configModule;
125
+ } catch {
126
+ logger.warn("No 'vaderjs.config.js' found, using defaults.");
127
+ return {};
128
+ }
129
+ }
130
+
131
+ function debounce(fn: Function, delay: number) {
132
+ let timeoutId: Timer;
133
+ return (...args: any[]) => {
134
+ clearTimeout(timeoutId);
135
+ timeoutId = setTimeout(() => fn(...args), delay);
136
+ };
137
+ }
138
+
139
+ // Find all relevant source directories in the project
140
+ function findSourceDirectories(): string[] {
141
+ const dirs = new Set<string>();
142
+
143
+ // Always include project root
144
+ dirs.add(PROJECT_ROOT);
145
+
146
+ // Add common source directories if they exist
147
+ const commonDirs = ['src', 'app', 'pages', 'components', 'public', 'styles'];
148
+ for (const dir of commonDirs) {
149
+ const fullPath = path.join(PROJECT_ROOT, dir);
150
+ if (fsSync.existsSync(fullPath) && fsSync.statSync(fullPath).isDirectory()) {
151
+ dirs.add(fullPath);
152
+ }
153
+ }
154
+
155
+ return Array.from(dirs);
156
+ }
157
+
158
+ async function handleRequestSimple(req: Request) {
159
+ const url = new URL(req.url);
160
+ const pathname = decodeURIComponent(url.pathname);
161
+
162
+ // Add trailing slash for directories without extensions
163
+ if (!pathname.endsWith('/') && !path.extname(pathname)) {
164
+ return Response.redirect(`${pathname}/`, 308);
165
+ }
166
+
167
+ // 1️⃣ STATIC FILES (with extension)
168
+ if (path.extname(pathname)) {
169
+ // Try exact path first: /styles.css β†’ /dist/styles.css
170
+ let staticPath = path.join(DIST_DIR, pathname.slice(1));
171
+ let file = Bun.file(staticPath);
172
+
173
+ if (await file.exists()) {
174
+ return new Response(file, {
175
+ headers: { "Content-Type": getContentType(path.extname(staticPath)) }
176
+ });
177
+ }
178
+
179
+ // Try route-relative: /login/foo.png β†’ /dist/login/foo.png
180
+ const segments = pathname.split('/').filter(Boolean);
181
+ if (segments.length > 1) {
182
+ staticPath = path.join(
183
+ DIST_DIR,
184
+ segments[0], // login
185
+ segments.slice(1).join('/')
186
+ );
187
+
188
+ file = Bun.file(staticPath);
189
+ if (await file.exists()) {
190
+ return new Response(file, {
191
+ headers: { "Content-Type": getContentType(path.extname(staticPath)) }
192
+ });
193
+ }
194
+ }
195
+
196
+ return new Response("Not Found", { status: 404 });
197
+ }
198
+
199
+ // 2️⃣ ROUTE HTML: /login β†’ /dist/login/index.html
200
+ const routeHtml = path.join(
201
+ DIST_DIR,
202
+ pathname === '/' ? '' : pathname.slice(1),
203
+ 'index.html'
204
+ );
205
+
206
+ if (await Bun.file(routeHtml).exists()) {
207
+ return new Response(Bun.file(routeHtml), {
208
+ headers: { "Content-Type": "text/html" }
209
+ });
210
+ }
211
+
212
+ // 3️⃣ SPA FALLBACK
213
+ const rootHtml = path.join(DIST_DIR, 'index.html');
214
+ return new Response(Bun.file(rootHtml), {
215
+ headers: { "Content-Type": "text/html" }
216
+ });
217
+ }
218
+
219
+ // Helper function to get content type from file extension
220
+ function getContentType(ext: string): string {
221
+ const contentTypes: Record<string, string> = {
222
+ '.html': 'text/html',
223
+ '.htm': 'text/html',
224
+ '.js': 'text/javascript',
225
+ '.mjs': 'text/javascript',
226
+ '.cjs': 'text/javascript',
227
+ '.json': 'application/json',
228
+ '.css': 'text/css',
229
+ '.png': 'image/png',
230
+ '.jpg': 'image/jpeg',
231
+ '.jpeg': 'image/jpeg',
232
+ '.gif': 'image/gif',
233
+ '.svg': 'image/svg+xml',
234
+ '.ico': 'image/x-icon',
235
+ '.webp': 'image/webp',
236
+ '.woff': 'font/woff',
237
+ '.woff2': 'font/woff2',
238
+ '.ttf': 'font/ttf',
239
+ '.eot': 'application/vnd.ms-fontobject',
240
+ '.otf': 'font/otf',
241
+ '.mp4': 'video/mp4',
242
+ '.webm': 'video/webm',
243
+ '.mp3': 'audio/mpeg',
244
+ '.wav': 'audio/wav',
245
+ '.ogg': 'audio/ogg',
246
+ '.pdf': 'application/pdf',
247
+ '.txt': 'text/plain',
248
+ '.csv': 'text/csv',
249
+ '.xml': 'application/xml',
250
+ '.zip': 'application/zip',
251
+ '.gz': 'application/gzip',
252
+ '.tar': 'application/x-tar',
253
+ '.rar': 'application/vnd.rar',
254
+ '.7z': 'application/x-7z-compressed'
255
+ };
256
+
257
+ return contentTypes[ext] || 'application/octet-stream';
258
+ }
259
+
260
+ // Clean up all watchers
261
+ function cleanupWatchers() {
262
+ for (const [dir, watcher] of watchers) {
263
+ try {
264
+ watcher.close();
265
+ } catch (err) {
266
+ // Ignore
267
+ }
268
+ }
269
+ watchers.clear();
270
+ }
271
+
272
+ // DEV SERVER
273
+ export default async function runDevServer() {
274
+ const config = await loadConfig();
275
+
276
+ // Initial build
277
+ console.log('πŸ”¨ Building project...');
278
+ await buildAll(true);
279
+
280
+ // Debug build output
281
+ console.log('\nπŸ“¦ Build output:');
282
+ if (fsSync.existsSync(DIST_DIR)) {
283
+ const items = fsSync.readdirSync(DIST_DIR);
284
+ console.log(`Dist root contains: ${items.join(', ')}`);
285
+ }
286
+
287
+ const clients = new Set<WebSocket>();
288
+ const port = config.port || 3000;
289
+
290
+ logger.info(`πŸš€ Starting dev server at http://localhost:${port}`);
291
+ logger.info(`πŸ“ Project root: ${PROJECT_ROOT}`);
292
+ logger.info(`πŸ“¦ Dist directory: ${DIST_DIR}`);
293
+ logger.info(`🌐 Build type: ${config.build_type || 'spa'}`);
294
+
295
+ const server = serve({
296
+ port,
297
+ fetch: async (req) => {
298
+ // handle /__hmr
299
+ const url = new URL(req.url);
300
+ if (url.pathname === '/__hmr') {
301
+ server.upgrade(req);
302
+ return new Response(null, { status: 101 });
303
+ }
304
+ return handleRequestSimple(req);
305
+ },
306
+ websocket: {
307
+ open: (ws) => {
308
+ clients.add(ws);
309
+ console.log(`πŸ”Œ WebSocket connected. Total clients: ${clients.size}`);
310
+ },
311
+ close: (ws) => {
312
+ clients.delete(ws);
313
+ console.log(`πŸ”Œ WebSocket disconnected. Total clients: ${clients.size}`);
314
+ },
315
+ message: (ws, message) => {
316
+ console.log(`πŸ“¨ WebSocket message: ${message}`);
317
+ }
318
+ },
319
+ });
320
+
321
+ const debouncedBuild = debounce(async () => {
322
+ try {
323
+ console.log('\nπŸ”„ Changes detected, rebuilding...');
324
+ await buildAll(true);
325
+
326
+ // Notify all connected WebSocket clients to reload
327
+ console.log(`πŸ“’ Notifying ${clients.size} clients to reload`);
328
+ for (const client of clients) {
329
+ if (client.readyState === 1) { // WebSocket.OPEN
330
+ client.send("reload");
331
+ }
332
+ }
333
+ logger.info("βœ… Rebuilt and reloaded");
334
+ } catch (e) {
335
+ logger.error("❌ Rebuild failed:", e);
336
+ }
337
+ }, 500); // Increased debounce time to 500ms
338
+
339
+ // Find and watch source directories
340
+ const watchDirs = findSourceDirectories();
341
+ console.log(`πŸ‘€ Watching directories: ${watchDirs.map(d => path.relative(PROJECT_ROOT, d)).join(', ')}`);
342
+
343
+ for (const dir of watchDirs) {
344
+ safeWatch(dir, debouncedBuild);
345
+ }
346
+
347
+ // Handle graceful shutdown
348
+ process.on('SIGINT', () => {
349
+ console.log('\nπŸ‘‹ Shutting down...');
350
+ cleanupWatchers();
351
+ server.stop();
352
+ process.exit(0);
353
+ });
354
+
355
+ process.on('SIGTERM', () => {
356
+ console.log('\nπŸ‘‹ Shutting down...');
357
+ cleanupWatchers();
358
+ server.stop();
359
+ process.exit(0);
360
+ });
361
+
362
+ // Also handle uncaught errors
363
+ process.on('uncaughtException', (err) => {
364
+ logger.error('Uncaught exception:', err);
365
+ cleanupWatchers();
366
+ server.stop();
367
+ process.exit(1);
368
+ });
369
+ }
370
+
371
+ // PROD SERVER
372
+ export async function runProdServer() {
373
+ const config = await loadConfig();
374
+ const port = config.port || 3000;
375
+
376
+ logger.info(`πŸš€ Serving production build from /dist on http://localhost:${port}`);
377
+
378
+ serve({
379
+ port,
380
+ fetch: async (req) => {
381
+ return handleRequestSimple(req);
382
+ }
383
+ });
384
+ }