vaderjs-native 1.0.13 → 1.0.15
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 +122 -83
- package/cli.ts +586 -196
- package/index.ts +73 -1
- package/main.ts +321 -161
- package/package.json +3 -2
- package/templates/i.txt +1 -0
- package/plugins/tailwind.ts +0 -53
package/index.ts
CHANGED
|
@@ -1802,7 +1802,78 @@ export function platform(): "windows" | "android" | "web" {
|
|
|
1802
1802
|
// 3. Fallback to web
|
|
1803
1803
|
return "web";
|
|
1804
1804
|
}
|
|
1805
|
-
|
|
1805
|
+
/**
|
|
1806
|
+
* Creates a Vader.js component with automatic prop memoization.
|
|
1807
|
+
* @template P - Component props type
|
|
1808
|
+
* @param {(props: P) => VNode} renderFn - The component render function
|
|
1809
|
+
* @returns {(props: P) => VNode} A memoized component function
|
|
1810
|
+
*/
|
|
1811
|
+
export function component<P extends object>( renderFn: (props: P) => VNode): (props: P) => VNode {
|
|
1812
|
+
// Create a wrapper function that will be the actual component
|
|
1813
|
+
const ComponentWrapper = (props: P): VNode => {
|
|
1814
|
+
// Check if props have changed
|
|
1815
|
+
let fiber = wipFiber;
|
|
1816
|
+
while (fiber && fiber.type !== ComponentWrapper) {
|
|
1817
|
+
fiber = fiber.alternate;
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
const prevProps = fiber?.alternate?.props || {};
|
|
1821
|
+
const nextProps = props;
|
|
1822
|
+
|
|
1823
|
+
// Create a simple props comparison
|
|
1824
|
+
// For now, we'll do a shallow comparison of props
|
|
1825
|
+
let shouldUpdate = false;
|
|
1826
|
+
|
|
1827
|
+
// Check if props count changed
|
|
1828
|
+
const prevKeys = Object.keys(prevProps);
|
|
1829
|
+
const nextKeys = Object.keys(nextProps);
|
|
1830
|
+
|
|
1831
|
+
if (prevKeys.length !== nextKeys.length) {
|
|
1832
|
+
shouldUpdate = true;
|
|
1833
|
+
} else {
|
|
1834
|
+
// Check each prop
|
|
1835
|
+
for (const key of nextKeys) {
|
|
1836
|
+
if (nextProps[key] !== prevProps[key]) {
|
|
1837
|
+
shouldUpdate = true;
|
|
1838
|
+
break;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// Mark fiber for memoization
|
|
1844
|
+
const currentFiber = wipFiber;
|
|
1845
|
+
if (currentFiber) {
|
|
1846
|
+
currentFiber.propsCache = nextProps;
|
|
1847
|
+
currentFiber.__compareProps = (prev: P, next: P) => {
|
|
1848
|
+
const prevKeys = Object.keys(prev);
|
|
1849
|
+
const nextKeys = Object.keys(next);
|
|
1850
|
+
|
|
1851
|
+
if (prevKeys.length !== nextKeys.length) return false;
|
|
1852
|
+
|
|
1853
|
+
for (const key of nextKeys) {
|
|
1854
|
+
if (next[key] !== prev[key]) return false;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
return true;
|
|
1858
|
+
};
|
|
1859
|
+
|
|
1860
|
+
currentFiber.__skipMemo = !shouldUpdate;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
// If props haven't changed, return the previous fiber's children
|
|
1864
|
+
if (!shouldUpdate && fiber?.alternate?.child) {
|
|
1865
|
+
return fiber.alternate.child.props.children[0];
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
// Otherwise render with new props
|
|
1869
|
+
return renderFn(props);
|
|
1870
|
+
};
|
|
1871
|
+
|
|
1872
|
+
// Set display name for debugging
|
|
1873
|
+
(ComponentWrapper as any).displayName = name;
|
|
1874
|
+
|
|
1875
|
+
return ComponentWrapper;
|
|
1876
|
+
}
|
|
1806
1877
|
const Vader = {
|
|
1807
1878
|
render,
|
|
1808
1879
|
createElement,
|
|
@@ -1826,6 +1897,7 @@ const Vader = {
|
|
|
1826
1897
|
Link,
|
|
1827
1898
|
showToast,
|
|
1828
1899
|
platform,
|
|
1900
|
+
component,
|
|
1829
1901
|
App
|
|
1830
1902
|
};
|
|
1831
1903
|
|
package/main.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import { colors } from "./cli/logger";
|
|
4
3
|
import { build, serve } from "bun";
|
|
5
4
|
import fs from "fs/promises";
|
|
6
5
|
import fsSync from "fs";
|
|
7
6
|
import path from "path";
|
|
8
|
-
import {
|
|
7
|
+
import { initProject, addPlugin, listPlugins, removePlugin } from "./cli";
|
|
9
8
|
import { logger, timedStep } from "./cli/logger";
|
|
9
|
+
import { colors } from "./cli/logger";
|
|
10
|
+
import runDevServer from "./cli/web/server";
|
|
11
|
+
import { runProdServer } from "./cli/web/server";
|
|
12
|
+
import { androidDev } from "./cli/android/dev.js";
|
|
13
|
+
import { buildAndroid } from "./cli/android/build";
|
|
14
|
+
import { buildWindows } from "./cli/windows/build";
|
|
15
|
+
import openWinApp from "./cli/windows/dev";
|
|
16
|
+
import { Config } from "./config";
|
|
10
17
|
|
|
11
18
|
// --- CONSTANTS ---
|
|
12
19
|
const PROJECT_ROOT = process.cwd();
|
|
@@ -17,17 +24,157 @@ const SRC_DIR = path.join(PROJECT_ROOT, "src");
|
|
|
17
24
|
const VADER_SRC_PATH = path.join(PROJECT_ROOT, "node_modules", "vaderjs-native", "index.ts");
|
|
18
25
|
const TEMP_SRC_DIR = path.join(PROJECT_ROOT, ".vader_temp_src");
|
|
19
26
|
|
|
20
|
-
// --- CACHE SYSTEM
|
|
27
|
+
// --- CACHE SYSTEM ---
|
|
21
28
|
const buildCache = new Map<string, { mtime: number; hash: string }>();
|
|
22
29
|
const configCache = new Map<string, { config: Config; mtime: number }>();
|
|
23
30
|
let config: Config = {};
|
|
24
31
|
let htmlInjections: string[] = [];
|
|
25
32
|
|
|
33
|
+
// --- SIMPLIFIED WATCHER ---
|
|
34
|
+
class FileWatcher {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.watchers = new Map();
|
|
37
|
+
this.onChangeCallbacks = [];
|
|
38
|
+
this.isRebuilding = false;
|
|
39
|
+
this.lastRebuildTime = 0;
|
|
40
|
+
this.REBUILD_COOLDOWN = 1000; // 1 second cooldown between rebuilds
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
shouldIgnorePath(filePath) {
|
|
44
|
+
const normalized = path.normalize(filePath);
|
|
45
|
+
// Ignore dist folder and its contents
|
|
46
|
+
if (normalized.includes(path.normalize(DIST_DIR))) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
// Ignore node_modules
|
|
50
|
+
if (normalized.includes(path.normalize('node_modules'))) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
// Ignore .git folder
|
|
54
|
+
if (normalized.includes(path.normalize('.git'))) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
// Ignore the temporary source directory
|
|
58
|
+
if (normalized.includes(path.normalize(TEMP_SRC_DIR))) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async watchDirectory(dirPath, recursive = true) {
|
|
65
|
+
// Skip if directory should be ignored
|
|
66
|
+
if (this.shouldIgnorePath(dirPath) || !fsSync.existsSync(dirPath)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// Close existing watcher if any
|
|
72
|
+
if (this.watchers.has(dirPath)) {
|
|
73
|
+
try {
|
|
74
|
+
this.watchers.get(dirPath).close();
|
|
75
|
+
} catch (err) {
|
|
76
|
+
// Ignore close errors
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Create new watcher
|
|
81
|
+
const watcher = fsSync.watch(dirPath, { recursive }, (eventType, filename) => {
|
|
82
|
+
if (!filename) return;
|
|
83
|
+
|
|
84
|
+
const changedFile = path.join(dirPath, filename);
|
|
85
|
+
const normalizedChanged = path.normalize(changedFile);
|
|
86
|
+
|
|
87
|
+
// Skip if file should be ignored
|
|
88
|
+
if (this.shouldIgnorePath(normalizedChanged)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check if this is a file we care about
|
|
93
|
+
if (this.shouldTriggerRebuild(normalizedChanged)) {
|
|
94
|
+
logger.info(`File changed: ${path.relative(PROJECT_ROOT, normalizedChanged)}`);
|
|
95
|
+
|
|
96
|
+
// Only trigger if not already rebuilding and cooldown has passed
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
if (!this.isRebuilding && (now - this.lastRebuildTime) > this.REBUILD_COOLDOWN) {
|
|
99
|
+
this.triggerChange(normalizedChanged);
|
|
100
|
+
} else if (this.isRebuilding) {
|
|
101
|
+
logger.info(`Skipping rebuild - already rebuilding`);
|
|
102
|
+
} else {
|
|
103
|
+
logger.info(`Skipping rebuild - cooldown period`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
watcher.on('error', (err) => {
|
|
109
|
+
logger.warn(`Watcher error on ${dirPath}:`, err.message);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
this.watchers.set(dirPath, watcher);
|
|
113
|
+
|
|
114
|
+
logger.info(`Watching directory: ${path.relative(PROJECT_ROOT, dirPath)}`);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
logger.warn(`Could not watch directory ${dirPath}:`, err.message);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
shouldTriggerRebuild(filePath) {
|
|
121
|
+
// Only trigger rebuild for specific file types
|
|
122
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
123
|
+
const triggerExtensions = ['.js', '.jsx', '.ts', '.tsx', '.css', '.html', '.json', '.config.js', '.config.ts'];
|
|
124
|
+
return triggerExtensions.includes(ext) || ext === '';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
triggerChange(filePath) {
|
|
128
|
+
for (const callback of this.onChangeCallbacks) {
|
|
129
|
+
try {
|
|
130
|
+
callback(filePath);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
logger.error("Change callback error:", err);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
onChange(callback) {
|
|
138
|
+
this.onChangeCallbacks.push(callback);
|
|
139
|
+
return () => {
|
|
140
|
+
const index = this.onChangeCallbacks.indexOf(callback);
|
|
141
|
+
if (index > -1) this.onChangeCallbacks.splice(index, 1);
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
setRebuilding(state) {
|
|
146
|
+
this.isRebuilding = state;
|
|
147
|
+
if (state) {
|
|
148
|
+
this.lastRebuildTime = Date.now();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
clear() {
|
|
153
|
+
for (const [dir, watcher] of this.watchers) {
|
|
154
|
+
try {
|
|
155
|
+
watcher.close();
|
|
156
|
+
} catch (err) {
|
|
157
|
+
// Ignore close errors
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
this.watchers.clear();
|
|
161
|
+
this.onChangeCallbacks = [];
|
|
162
|
+
this.isRebuilding = false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const watcher = new FileWatcher();
|
|
167
|
+
|
|
26
168
|
// --- CONFIG & PLUGIN SYSTEM ---
|
|
27
169
|
interface VaderAPI {
|
|
28
170
|
runCommand: (cmd: string | string[]) => Promise<void>;
|
|
29
171
|
injectHTML: (content: string) => void;
|
|
30
|
-
log:
|
|
172
|
+
log: {
|
|
173
|
+
warn: (msg: string) => void;
|
|
174
|
+
info: (msg: string) => void;
|
|
175
|
+
success: (msg: string) => void;
|
|
176
|
+
step: (msg: string) => void;
|
|
177
|
+
};
|
|
31
178
|
getProjectRoot: () => string;
|
|
32
179
|
getDistDir: () => string;
|
|
33
180
|
getPublicDir: () => string;
|
|
@@ -40,7 +187,12 @@ const vaderAPI: VaderAPI = {
|
|
|
40
187
|
await p.exited;
|
|
41
188
|
},
|
|
42
189
|
injectHTML: (content) => htmlInjections.push(content),
|
|
43
|
-
log:
|
|
190
|
+
log: {
|
|
191
|
+
warn: (msg) => logger.warn(msg),
|
|
192
|
+
info: (msg) => logger.info(msg),
|
|
193
|
+
success: (msg) => logger.success(msg),
|
|
194
|
+
step: (msg) => logger.step(msg)
|
|
195
|
+
},
|
|
44
196
|
getProjectRoot: () => PROJECT_ROOT,
|
|
45
197
|
getDistDir: () => DIST_DIR,
|
|
46
198
|
getPublicDir: () => PUBLIC_DIR,
|
|
@@ -51,8 +203,8 @@ export async function loadConfig(projectDir?: string): Promise<Config> {
|
|
|
51
203
|
projectDir = projectDir || process.cwd();
|
|
52
204
|
const configKey = `config-${projectDir}`;
|
|
53
205
|
|
|
54
|
-
const configPathTs = path.join(projectDir, "vaderjs.config.ts");
|
|
55
206
|
const configPathJs = path.join(projectDir, "vaderjs.config.js");
|
|
207
|
+
const configPathTs = path.join(projectDir, "vaderjs.config.ts");
|
|
56
208
|
|
|
57
209
|
let configPath: string | null = null;
|
|
58
210
|
let stat: fs.Stats | null = null;
|
|
@@ -160,6 +312,20 @@ async function parallelForEach<T>(
|
|
|
160
312
|
}
|
|
161
313
|
}
|
|
162
314
|
|
|
315
|
+
async function copyIfNeeded(src: string, dest: string): Promise<void> {
|
|
316
|
+
const cacheKey = `copy-${src}`;
|
|
317
|
+
const stat = await fs.stat(src).catch(() => null);
|
|
318
|
+
if (!stat) return;
|
|
319
|
+
|
|
320
|
+
const cached = buildCache.get(cacheKey);
|
|
321
|
+
if (cached && cached.mtime === stat.mtimeMs) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
await fs.copyFile(src, dest);
|
|
326
|
+
buildCache.set(cacheKey, { mtime: stat.mtimeMs, hash: await getFileHash(src) });
|
|
327
|
+
}
|
|
328
|
+
|
|
163
329
|
// --- BUILD LOGIC ---
|
|
164
330
|
|
|
165
331
|
/**
|
|
@@ -380,31 +546,42 @@ async function copyPublicAssetsRecursive(srcDir: string, destDir: string): Promi
|
|
|
380
546
|
});
|
|
381
547
|
}
|
|
382
548
|
|
|
383
|
-
async function
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
|
|
549
|
+
async function buildAppEntrypoint(entryPath: string, name: string, isDev = false): Promise<void> {
|
|
550
|
+
const outDir = path.join(DIST_DIR, name === 'index' ? '' : name);
|
|
551
|
+
const outJsPath = path.join(outDir, 'index.js');
|
|
552
|
+
const outHtmlPath = path.join(outDir, 'index.html');
|
|
387
553
|
|
|
388
|
-
|
|
389
|
-
if (
|
|
554
|
+
// Check if rebuild is needed
|
|
555
|
+
if (!isDev && !(await needsRebuild(entryPath, outJsPath))) {
|
|
556
|
+
logger.info(`Entrypoint "${name}" is up to date`);
|
|
390
557
|
return;
|
|
391
558
|
}
|
|
392
559
|
|
|
393
|
-
await fs.
|
|
394
|
-
buildCache.set(cacheKey, { mtime: stat.mtimeMs, hash: await getFileHash(src) });
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Step 6: Build app entrypoints with incremental compilation
|
|
399
|
-
*/
|
|
400
|
-
async function buildAppEntrypoints(isDev = false): Promise<void> {
|
|
401
|
-
if (!fsSync.existsSync(APP_DIR)) {
|
|
402
|
-
logger.warn("No '/app' directory found, skipping app entrypoint build.");
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
560
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
405
561
|
|
|
406
|
-
|
|
562
|
+
// --- CSS HANDLING ---
|
|
563
|
+
const cssLinks: string[] = [];
|
|
564
|
+
let content = await fs.readFile(entryPath, "utf8");
|
|
565
|
+
const cssImports = [...content.matchAll(/import\s+['"](.*\.css)['"]/g)];
|
|
566
|
+
|
|
567
|
+
await parallelForEach(cssImports, async (match) => {
|
|
568
|
+
const cssImportPath = match[1];
|
|
569
|
+
const sourceCssPath = path.resolve(path.dirname(entryPath), cssImportPath);
|
|
570
|
+
|
|
571
|
+
try {
|
|
572
|
+
await fs.access(sourceCssPath);
|
|
573
|
+
const relativeCssPath = path.relative(APP_DIR, sourceCssPath);
|
|
574
|
+
const destCssPath = path.join(DIST_DIR, relativeCssPath);
|
|
575
|
+
|
|
576
|
+
await copyIfNeeded(sourceCssPath, destCssPath);
|
|
577
|
+
const htmlRelativePath = path.relative(outDir, destCssPath).replace(/\\/g, '/');
|
|
578
|
+
cssLinks.push(`<link rel="stylesheet" href="${htmlRelativePath}">`);
|
|
579
|
+
} catch {
|
|
580
|
+
logger.warn(`CSS file not found: ${sourceCssPath}`);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
407
583
|
|
|
584
|
+
// --- HTML GENERATION ---
|
|
408
585
|
const devClientScript = isDev
|
|
409
586
|
? `<script>
|
|
410
587
|
const ws = new WebSocket("ws://" + location.host + "/__hmr");
|
|
@@ -415,71 +592,7 @@ async function buildAppEntrypoints(isDev = false): Promise<void> {
|
|
|
415
592
|
</script>`
|
|
416
593
|
: "";
|
|
417
594
|
|
|
418
|
-
|
|
419
|
-
const entrypoints: Array<{ name: string; path: string }> = [];
|
|
420
|
-
function findEntrypoints(dir: string, baseDir = ""): void {
|
|
421
|
-
const items = fsSync.readdirSync(dir, { withFileTypes: true });
|
|
422
|
-
for (const item of items) {
|
|
423
|
-
const fullPath = path.join(dir, item.name);
|
|
424
|
-
const relativePath = path.join(baseDir, item.name);
|
|
425
|
-
|
|
426
|
-
if (item.isDirectory()) {
|
|
427
|
-
findEntrypoints(fullPath, relativePath);
|
|
428
|
-
} else if (item.name === "index.tsx" || item.name === "index.jsx") {
|
|
429
|
-
const name = baseDir || 'index';
|
|
430
|
-
entrypoints.push({ name, path: fullPath });
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
findEntrypoints(APP_DIR);
|
|
436
|
-
|
|
437
|
-
if (entrypoints.length === 0) {
|
|
438
|
-
logger.info("No app entrypoints found.");
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// Pre-load Vader source for faster processing
|
|
443
|
-
const vaderSourcePromise = fs.readFile(VADER_SRC_PATH, "utf8");
|
|
444
|
-
|
|
445
|
-
// Process entrypoints in parallel
|
|
446
|
-
await parallelForEach(entrypoints, async ({ name, path: entryPath }) => {
|
|
447
|
-
const outDir = path.join(DIST_DIR, name === 'index' ? '' : name);
|
|
448
|
-
const outJsPath = path.join(outDir, 'index.js');
|
|
449
|
-
const outHtmlPath = path.join(outDir, 'index.html');
|
|
450
|
-
|
|
451
|
-
// Check if rebuild is needed
|
|
452
|
-
if (!isDev && !(await needsRebuild(entryPath, outJsPath))) {
|
|
453
|
-
logger.info(`Entrypoint "${name}" is up to date`);
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
await fs.mkdir(outDir, { recursive: true });
|
|
458
|
-
|
|
459
|
-
// --- CSS HANDLING ---
|
|
460
|
-
const cssLinks: string[] = [];
|
|
461
|
-
let content = await fs.readFile(entryPath, "utf8");
|
|
462
|
-
const cssImports = [...content.matchAll(/import\s+['"](.*\.css)['"]/g)];
|
|
463
|
-
|
|
464
|
-
await parallelForEach(cssImports, async (match) => {
|
|
465
|
-
const cssImportPath = match[1];
|
|
466
|
-
const sourceCssPath = path.resolve(path.dirname(entryPath), cssImportPath);
|
|
467
|
-
|
|
468
|
-
try {
|
|
469
|
-
await fs.access(sourceCssPath);
|
|
470
|
-
const relativeCssPath = path.relative(APP_DIR, sourceCssPath);
|
|
471
|
-
const destCssPath = path.join(DIST_DIR, relativeCssPath);
|
|
472
|
-
|
|
473
|
-
await copyIfNeeded(sourceCssPath, destCssPath);
|
|
474
|
-
const htmlRelativePath = path.relative(outDir, destCssPath).replace(/\\/g, '/');
|
|
475
|
-
cssLinks.push(`<link rel="stylesheet" href="${htmlRelativePath}">`);
|
|
476
|
-
} catch {
|
|
477
|
-
logger.warn(`CSS file not found: ${sourceCssPath}`);
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
// --- HTML GENERATION ---
|
|
482
|
-
const windowsStyle = globalThis.isBuildingForWindows ? `
|
|
595
|
+
const windowsStyle = globalThis.isBuildingForWindows ? `
|
|
483
596
|
<style>
|
|
484
597
|
.title-bar {
|
|
485
598
|
display: flex;
|
|
@@ -493,8 +606,8 @@ async function buildAppEntrypoints(isDev = false): Promise<void> {
|
|
|
493
606
|
-webkit-app-region: no-drag;
|
|
494
607
|
}
|
|
495
608
|
</style>` : '';
|
|
496
|
-
|
|
497
|
-
|
|
609
|
+
|
|
610
|
+
const htmlContent = `<!DOCTYPE html>
|
|
498
611
|
<html lang="en">
|
|
499
612
|
<head>
|
|
500
613
|
<meta charset="UTF-8" />
|
|
@@ -506,52 +619,71 @@ async function buildAppEntrypoints(isDev = false): Promise<void> {
|
|
|
506
619
|
</head>
|
|
507
620
|
<body>
|
|
508
621
|
<div id="app"></div>
|
|
509
|
-
<script src="./index.js"></script>
|
|
510
|
-
${devClientScript}
|
|
622
|
+
<script src="./index.js"></script>
|
|
511
623
|
</body>
|
|
512
624
|
</html>`;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
// --- FIX IMPORT PATHS IN JS ---
|
|
539
|
-
let jsContent = await fs.readFile(outJsPath, "utf8");
|
|
540
|
-
const vaderSource = await vaderSourcePromise;
|
|
541
|
-
|
|
542
|
-
// Replace Vader import with actual source
|
|
543
|
-
jsContent = jsContent.replace(
|
|
544
|
-
/import\s+\*\s+as\s+Vader\s+from\s+['"]vaderjs['"];?/,
|
|
545
|
-
vaderSource
|
|
546
|
-
);
|
|
547
|
-
|
|
548
|
-
await fs.writeFile(outJsPath, jsContent);
|
|
549
|
-
|
|
550
|
-
// Update cache
|
|
551
|
-
const stat = await fs.stat(entryPath);
|
|
552
|
-
const hash = await getFileHash(entryPath);
|
|
553
|
-
buildCache.set(`${entryPath}:${outJsPath}`, { mtime: stat.mtimeMs, hash });
|
|
625
|
+
|
|
626
|
+
await fs.writeFile(outHtmlPath, htmlContent);
|
|
627
|
+
|
|
628
|
+
// --- JS BUILD ---
|
|
629
|
+
await build({
|
|
630
|
+
entrypoints: [entryPath],
|
|
631
|
+
outdir: outDir,
|
|
632
|
+
target: "browser",
|
|
633
|
+
minify: !isDev,
|
|
634
|
+
sourcemap: isDev ? "inline" : "external",
|
|
635
|
+
jsxFactory: "Vader.createElement",
|
|
636
|
+
jsxFragment: "Fragment",
|
|
637
|
+
plugins: [publicAssetPlugin()],
|
|
638
|
+
loader: {
|
|
639
|
+
'.js': 'jsx',
|
|
640
|
+
'.ts': 'tsx',
|
|
641
|
+
'.css': 'text',
|
|
642
|
+
},
|
|
643
|
+
define: isDev ? {
|
|
644
|
+
'process.env.NODE_ENV': '"development"'
|
|
645
|
+
} : {
|
|
646
|
+
'process.env.NODE_ENV': '"production"'
|
|
647
|
+
}
|
|
554
648
|
});
|
|
649
|
+
|
|
650
|
+
// --- FIX IMPORT PATHS IN JS ---
|
|
651
|
+
let jsContent = await fs.readFile(outJsPath, "utf8");
|
|
652
|
+
const vaderSource = await fs.readFile(VADER_SRC_PATH, "utf8");
|
|
653
|
+
|
|
654
|
+
// Replace Vader import with actual source
|
|
655
|
+
jsContent = jsContent.replace(
|
|
656
|
+
/import\s+\*\s+as\s+Vader\s+from\s+['"]vaderjs['"];?/,
|
|
657
|
+
vaderSource
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
await fs.writeFile(outJsPath, jsContent);
|
|
661
|
+
|
|
662
|
+
// Update cache
|
|
663
|
+
const stat = await fs.stat(entryPath);
|
|
664
|
+
const hash = await getFileHash(entryPath);
|
|
665
|
+
buildCache.set(`${entryPath}:${outJsPath}`, { mtime: stat.mtimeMs, hash });
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
async function buildAppEntrypoints(isDev = false): Promise<void> {
|
|
669
|
+
if (!fsSync.existsSync(APP_DIR)) {
|
|
670
|
+
logger.warn("No '/app' directory found, skipping app entrypoint build.");
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
await fs.mkdir(DIST_DIR, { recursive: true });
|
|
675
|
+
|
|
676
|
+
// Find all index.jsx/tsx files in app directory
|
|
677
|
+
const entries = fsSync.readdirSync(APP_DIR, { recursive: true })
|
|
678
|
+
.filter(file => /index\.(jsx|tsx)$/.test(file))
|
|
679
|
+
.map(file => ({
|
|
680
|
+
name: path.dirname(file) === '.' ? 'index' : path.dirname(file).replace(/\\/g, '/'),
|
|
681
|
+
path: path.join(APP_DIR, file)
|
|
682
|
+
}));
|
|
683
|
+
|
|
684
|
+
for (const { name, path: entryPath } of entries) {
|
|
685
|
+
await buildAppEntrypoint(entryPath, name, isDev);
|
|
686
|
+
}
|
|
555
687
|
}
|
|
556
688
|
|
|
557
689
|
// --- MAIN BUILD FUNCTION ---
|
|
@@ -602,15 +734,6 @@ export async function buildAll(isDev = false): Promise<void> {
|
|
|
602
734
|
logger.success(`Build completed in ${duration}ms. Output in ${DIST_DIR}`);
|
|
603
735
|
}
|
|
604
736
|
|
|
605
|
-
// --- IMPORTS ---
|
|
606
|
-
import runDevServer from "./cli/web/server";
|
|
607
|
-
import { runProdServer } from "./cli/web/server";
|
|
608
|
-
import { androidDev } from "./cli/android/dev.js";
|
|
609
|
-
import { buildAndroid } from "./cli/android/build";
|
|
610
|
-
import { buildWindows } from "vaderjs-native/cli/windows/build";
|
|
611
|
-
import openWinApp from "vaderjs-native/cli/windows/dev";
|
|
612
|
-
import { Config } from "vaderjs-native/config";
|
|
613
|
-
|
|
614
737
|
// --- SCRIPT ENTRYPOINT ---
|
|
615
738
|
async function main(): Promise<void> {
|
|
616
739
|
// Banner
|
|
@@ -622,19 +745,53 @@ async function main(): Promise<void> {
|
|
|
622
745
|
|____/____/_____/ /_____/ |_| |_|
|
|
623
746
|
${colors.reset}`);
|
|
624
747
|
|
|
625
|
-
// Load config with caching
|
|
626
|
-
config.port = config.port || 3000;
|
|
627
|
-
|
|
628
748
|
const command = process.argv[2];
|
|
629
|
-
const
|
|
749
|
+
const arg = process.argv[3];
|
|
630
750
|
|
|
631
751
|
// Set global flags
|
|
632
752
|
globalThis.isDev = command?.includes('dev') || false;
|
|
633
753
|
globalThis.isBuildingForWindows = command?.includes('windows') || false;
|
|
634
754
|
|
|
755
|
+
// Commands that don't require config
|
|
756
|
+
if (command === "init") {
|
|
757
|
+
await initProject(arg);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (command === "add") {
|
|
762
|
+
if (!arg) {
|
|
763
|
+
logger.error("Please specify a plugin to add.");
|
|
764
|
+
process.exit(1);
|
|
765
|
+
}
|
|
766
|
+
await addPlugin(arg);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Load config for runtime commands
|
|
771
|
+
config = await loadConfig();
|
|
772
|
+
config.port = config.port || 3000;
|
|
773
|
+
|
|
635
774
|
// Command router
|
|
636
775
|
const commandHandlers: Record<string, () => Promise<void>> = {
|
|
776
|
+
'add': async () => {
|
|
777
|
+
if (!arg) {
|
|
778
|
+
logger.error("Please specify a plugin to add.");
|
|
779
|
+
process.exit(1);
|
|
780
|
+
}
|
|
781
|
+
await addPlugin(arg);
|
|
782
|
+
},
|
|
783
|
+
'list_plugins': async () => {
|
|
784
|
+
await listPlugins();
|
|
785
|
+
},
|
|
786
|
+
'remove': async () => {
|
|
787
|
+
if (!arg) {
|
|
788
|
+
logger.error("Please specify a plugin to remove.");
|
|
789
|
+
process.exit(1);
|
|
790
|
+
}
|
|
791
|
+
await removePlugin(arg);
|
|
792
|
+
},
|
|
637
793
|
'dev': async () => {
|
|
794
|
+
globalThis.isDev = true;
|
|
638
795
|
await runDevServer("web");
|
|
639
796
|
},
|
|
640
797
|
'android:dev': async () => {
|
|
@@ -663,24 +820,27 @@ async function main(): Promise<void> {
|
|
|
663
820
|
'serve': async () => {
|
|
664
821
|
await buildAll(false);
|
|
665
822
|
await runProdServer();
|
|
666
|
-
}
|
|
667
|
-
'init': async () => {
|
|
668
|
-
await init().catch((e: Error) => {
|
|
669
|
-
console.error("Initialization failed:", e);
|
|
670
|
-
process.exit(1);
|
|
671
|
-
});
|
|
672
|
-
},
|
|
823
|
+
}
|
|
673
824
|
};
|
|
674
825
|
|
|
675
826
|
if (command && command in commandHandlers) {
|
|
676
827
|
await commandHandlers[command]();
|
|
677
|
-
} else if (command) {
|
|
678
|
-
logger.error(`Unknown command: ${command}`);
|
|
679
|
-
logger.info("Available commands: dev, android:dev, android:build, windows:dev, windows:build, build, serve, init");
|
|
680
|
-
process.exit(1);
|
|
681
828
|
} else {
|
|
682
|
-
logger.error(
|
|
683
|
-
logger.info(
|
|
829
|
+
logger.error(`Unknown command: ${command ?? ""}`);
|
|
830
|
+
logger.info(`
|
|
831
|
+
Available commands:
|
|
832
|
+
dev Start dev server
|
|
833
|
+
build Build for production
|
|
834
|
+
serve Build + serve production
|
|
835
|
+
init [dir] Create a new Vader project
|
|
836
|
+
add <plugin> Add a Vader plugin
|
|
837
|
+
remove <plugin> Remove a Vader plugin
|
|
838
|
+
list_plugins List currently installed Vaderjs plugins
|
|
839
|
+
android:dev Start Android development
|
|
840
|
+
android:build Build Android app
|
|
841
|
+
windows:dev Start Windows development
|
|
842
|
+
windows:build Build Windows app
|
|
843
|
+
`.trim());
|
|
684
844
|
process.exit(1);
|
|
685
845
|
}
|
|
686
846
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vaderjs-native",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "Build Native Applications using Vaderjs framework.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"vaderjs": "./main.ts"
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"ansi-colors": "latest",
|
|
16
16
|
"autoprefixer": "^10.4.23",
|
|
17
17
|
"postcss-cli": "^11.0.1",
|
|
18
|
-
"tailwindcss": "latest"
|
|
18
|
+
"tailwindcss": "latest",
|
|
19
|
+
"vaderjs-types":"latest"
|
|
19
20
|
}
|
|
20
21
|
}
|