vaderjs 2.2.6 → 2.3.0

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/main.js CHANGED
@@ -1,642 +1,456 @@
1
1
  #!/usr/bin/env bun
2
-
3
- import ansiColors from 'ansi-colors'
4
- import { Glob } from 'bun'
5
- const args = Bun.argv.slice(2)
6
- globalThis.isBuilding = false;
7
- import fs from 'fs'
8
- import { platform } from 'os'
9
- import path from 'path'
10
-
11
- let bunPath = 'bun'; // Default for Linux/Mac
12
- if (platform() === 'win32') {
13
- bunPath = 'bun'; // Bun path for Windows
14
- } else {
15
- bunPath = path.resolve(process.env.HOME || process.env.USERPROFILE, '.bun', 'bin', 'bun');
2
+ /**
3
+ * VaderJS Build & Development Script
4
+ *
5
+ * This script handles building the VaderJS framework, your application code,
6
+ * and serving it in a local development environment with live reloading.
7
+ *
8
+ * Commands:
9
+ * bun run vaderjs build - Builds the project for production.
10
+ * bun run vaderjs dev - Starts the dev server with HMR and file watching.
11
+ * bun run vaderjs serve - Builds and serves the production output.
12
+ */
13
+
14
+ import { build, serve } from "bun";
15
+ import fs from "fs/promises";
16
+ import fsSync from "fs";
17
+ import path from "path";
18
+ import { init } from "./cli";
19
+
20
+ // --- UTILITIES for a Sleek CLI ---
21
+
22
+ const colors = {
23
+ reset: "\x1b[0m",
24
+ red: "\x1b[31m",
25
+ green: "\x1b[32m",
26
+ yellow: "\x1b[33m",
27
+ blue: "\x1b[34m",
28
+ magenta: "\x1b[35m",
29
+ cyan: "\x1b[36m",
30
+ };
31
+
32
+ const logger = {
33
+ _log: (color, ...args) => console.log(color, ...args, colors.reset),
34
+ info: (...args) => logger._log(colors.cyan, "ℹ", ...args),
35
+ success: (...args) => logger._log(colors.green, "✅", ...args),
36
+ warn: (...args) => logger._log(colors.yellow, "⚠️", ...args),
37
+ error: (...args) => logger._log(colors.red, "❌", ...args),
38
+ step: (...args) => logger._log(colors.magenta, "\n🚀", ...args),
39
+ };
40
+
41
+ async function timedStep(name, fn) {
42
+ logger.step(`${name}...`);
43
+ const start = performance.now();
44
+ try {
45
+ await fn();
46
+ const duration = (performance.now() - start).toFixed(2);
47
+ logger.success(`Finished '${name}' in ${duration}ms`);
48
+ } catch (e) {
49
+ logger.error(`Error during '${name}':`, e.message);
50
+ process.exit(1);
51
+ }
16
52
  }
17
53
 
18
-
19
-
20
-
21
- if (!fs.existsSync(process.cwd() + '/app') && !args.includes('init')) {
22
- console.error(`App directory not found in ${process.cwd()}/app`)
23
- process.exit(1)
24
- }
25
- if (!fs.existsSync(process.cwd() + '/public')) {
26
- fs.mkdirSync(process.cwd() + '/public')
27
- }
28
- if (!fs.existsSync(process.cwd() + '/src')) {
29
- fs.mkdirSync(process.cwd() + '/src')
54
+ // --- CONSTANTS ---
55
+
56
+ const PROJECT_ROOT = process.cwd();
57
+ const APP_DIR = path.join(PROJECT_ROOT, "app");
58
+ const PUBLIC_DIR = path.join(PROJECT_ROOT, "public");
59
+ const DIST_DIR = path.join(PROJECT_ROOT, "dist");
60
+ const SRC_DIR = path.join(PROJECT_ROOT, "src");
61
+ const VADER_SRC_PATH = path.join(PROJECT_ROOT, "node_modules", "vaderjs", "index.ts");
62
+ const TEMP_SRC_DIR = path.join(PROJECT_ROOT, ".vader_temp_src");
63
+
64
+
65
+ // --- CONFIG & PLUGIN SYSTEM ---
66
+
67
+ let config = {};
68
+ let htmlInjections = [];
69
+
70
+ const vaderAPI = {
71
+ runCommand: async (cmd) => {
72
+ if (typeof cmd === "string") cmd = cmd.split(" ");
73
+ const p = Bun.spawn(cmd);
74
+ await p.exited;
75
+ },
76
+ injectHTML: (content) => htmlInjections.push(content),
77
+ log: (msg) => logger.info(`[Plugin] ${msg}`),
78
+ getProjectRoot: () => PROJECT_ROOT,
79
+ getDistDir: () => DIST_DIR,
80
+ getPublicDir: () => PUBLIC_DIR,
81
+ };
82
+
83
+ async function loadConfig() {
84
+ try {
85
+ const configModule = await import(path.join(PROJECT_ROOT, "vader.config.js"));
86
+ return configModule.default || configModule;
87
+ } catch {
88
+ logger.warn("No 'vader.config.js' found, using defaults.");
89
+ return {};
90
+ }
30
91
  }
31
- if (!fs.existsSync(process.cwd() + '/vader.config.ts')) {
32
- fs.writeFileSync(process.cwd() + '/vader.config.ts',
33
- `import defineConfig from 'vaderjs/config'
34
- export default defineConfig({
35
- port: 8080,
36
- host_provider: 'apache'
37
- })`)
38
- }
39
- var config = require(process.cwd() + '/vader.config.ts').default
40
- const mode = args.includes('dev') ? 'development' : args.includes('prod') || args.includes('build') ? 'production' : args.includes('init') ? 'init' : args.includes('serve') ? 'serve' : null;
41
- if (!mode) {
42
- console.log(`
43
- Usage:
44
- bun vaderjs serve - Start the server
45
- bun vaderjs dev - Start development server output in dist/
46
- bun vaderjs prod - Build for production output in dist/
47
- bun vaderjs init - Initialize a new vaderjs project
48
- `)
49
- process.exit(1)
92
+
93
+ export function defineConfig(config) {
94
+ return config;
50
95
  }
51
96
 
52
- if (mode === 'init') {
53
- if (fs.existsSync(process.cwd() + '/app')) {
54
- console.error('App directory already exists: just run `bun vaderjs dev` to start the development server')
55
- process.exit(1)
97
+ async function runPluginHook(hookName) {
98
+ if (!config.plugins) return;
99
+ for (const plugin of config.plugins) {
100
+ if (typeof plugin[hookName] === "function") {
101
+ try {
102
+ await plugin[hookName](vaderAPI);
103
+ } catch (e) {
104
+ logger.error(`Plugin hook error (${hookName} in ${plugin.name || 'anonymous plugin'}):`, e);
105
+ }
56
106
  }
57
- let counterText = await Bun.file(path.join(process.cwd(), "/node_modules/vaderjs/examples/counter/index.jsx")).text()
58
- await Bun.write(path.join(process.cwd(), "/app/index.jsx"), counterText)
59
- console.log('Initialized new vaderjs project: run `bun vaderjs dev` to start the development server')
60
- process.exit(0)
107
+ }
61
108
  }
62
109
 
63
- console.log(
64
- `VaderJS - v${require(process.cwd() + '/node_modules/vaderjs/package.json').version} 🚀
65
- Mode: ${mode}
66
- SSR: ${require(process.cwd() + '/vader.config.ts').default.ssr ? 'Enabled' : 'Disabled'}
67
- PORT: ${require(process.cwd() + '/vader.config.ts').default.port || 8080}
68
- ${mode == 'serve' ? `SSL: ${require(process.cwd() + '/vader.config.ts').default?.ssl?.enabled ? 'Enabled' : 'Disabled'} ` : ``}
69
- `
70
- )
71
110
 
111
+ // --- BUILD LOGIC ---
112
+
113
+ /**
114
+ * Step 1: Transpile and bundle the core vaderjs library.
115
+ */
116
+ async function buildVaderCore() {
117
+ if (!fsSync.existsSync(VADER_SRC_PATH)) {
118
+ logger.error("VaderJS source not found:", VADER_SRC_PATH);
119
+ throw new Error("Missing vaderjs dependency.");
120
+ }
121
+
122
+ await build({
123
+ entrypoints: [VADER_SRC_PATH],
124
+ outdir: path.join(DIST_DIR, "src", "vader"),
125
+ target: "browser",
126
+ minify: false,
127
+ sourcemap: "external",
128
+ jsxFactory: "e",
129
+ jsxFragment: "Fragment",
130
+ jsxImportSource: "vaderjs",
131
+ });
132
+ }
72
133
 
73
- let { port, host, host_provider } = require(process.cwd() + '/vader.config.ts').default
74
- if (host_provider === 'apache' && mode === 'development') {
75
- console.warn('Note: SSR will not work with Apache')
134
+ /**
135
+ * Step 2: Patches source code to remove server-side hook imports.
136
+ */
137
+ function patchHooksUsage(code) {
138
+ return code.replace(/import\s+{[^}]*use(State|Effect|Memo|Navigation)[^}]*}\s+from\s+['"]vaderjs['"];?\n?/g, "");
76
139
  }
77
- if (!fs.existsSync(process.cwd() + '/jsconfig.json')) {
78
- let json = {
79
- "compilerOptions": {
80
- "jsx": "react",
81
- "jsxFactory": "e",
82
- "jsxFragmentFactory": "Fragment",
83
- }
140
+
141
+ /**
142
+ * Step 3: Pre-processes all files in `/src` into a temporary directory.
143
+ */
144
+ async function preprocessSources(srcDir, tempDir) {
145
+ await fs.mkdir(tempDir, { recursive: true });
146
+ for (const entry of await fs.readdir(srcDir, { withFileTypes: true })) {
147
+ const srcPath = path.join(srcDir, entry.name);
148
+ const destPath = path.join(tempDir, entry.name);
149
+
150
+ if (entry.isDirectory()) {
151
+ await preprocessSources(srcPath, destPath);
152
+ } else if (/\.(tsx|jsx|ts|js)$/.test(entry.name)) {
153
+ let content = await fs.readFile(srcPath, "utf8");
154
+ content = patchHooksUsage(content);
155
+ await fs.writeFile(destPath, content);
156
+ } else {
157
+ await fs.copyFile(srcPath, destPath);
84
158
  }
85
- await Bun.write(process.cwd() + '/jsconfig.json', JSON.stringify(json, null, 4))
159
+ }
86
160
  }
87
161
 
88
- globalThis.bindes = []
89
- var fnmap = []
90
- const vader = {
91
- isDev: mode === 'development',
92
- onFileChange: (file, cb) => {
93
- fs.watch(file, cb)
94
- },
95
- runCommand: (cmd) => {
96
- return new Promise((resolve, reject) => {
97
- let c = Bun.spawn(cmd, {
98
- stdout: 'inherit',
99
- cwd: process.cwd(),
100
- onExit({ exitCode: code }) {
101
- if (code === 0) {
102
- resolve()
103
- } else {
104
- reject()
105
- }
106
- }
107
- })
108
-
109
-
110
-
111
-
112
- })
113
- },
114
- onBuildStart: (cb) => {
115
- if (!fnmap.find(v => v.code == cb.toString())) {
116
- fnmap.push({ code: cb.toString(), fn: cb })
117
- }
118
- },
119
- injectHTML: (html) => {
120
- bindes.push(html)
121
- globalThis.bindes = bindes
122
- },
162
+ /**
163
+ * Step 4: Build the application's source code from the preprocessed temp directory.
164
+ */
165
+ async function buildSrc() {
166
+ if (!fsSync.existsSync(SRC_DIR)) return;
167
+
168
+ if (fsSync.existsSync(TEMP_SRC_DIR)) {
169
+ await fs.rm(TEMP_SRC_DIR, { recursive: true, force: true });
170
+ }
171
+ await preprocessSources(SRC_DIR, TEMP_SRC_DIR);
172
+
173
+ const entrypoints = fsSync.readdirSync(TEMP_SRC_DIR, { recursive: true })
174
+ .map(file => path.join(TEMP_SRC_DIR, file))
175
+ .filter(file => /\.(ts|tsx|js|jsx)$/.test(file));
176
+
177
+ if (entrypoints.length === 0) {
178
+ logger.info("No source files found in /src to build.");
179
+ return;
180
+ }
181
+
182
+ await build({
183
+ entrypoints,
184
+ outdir: path.join(DIST_DIR, "src"),
185
+ root: TEMP_SRC_DIR,
186
+ naming: { entry: "[dir]/[name].js" },
187
+ jsxFactory: "e",
188
+ jsxFragment: "Fragment",
189
+ jsxImportSource: "vaderjs",
190
+ target: "browser",
191
+ minify: false,
192
+ external: ["vaderjs"],
193
+ });
123
194
  }
124
- const handleReplacements = (code) => {
125
- let lines = code.split('\n')
126
- let newLines = []
127
- for (let line of lines) {
128
- let hasImport = line.includes('import')
129
-
130
- if (hasImport && line.includes('public')) {
131
- // remove ../ from path
132
195
 
133
- line = line.replaceAll('../', '').replaceAll('./', '')
196
+ /**
197
+ * Step 5: Copy all assets from the `/public` directory to `/dist`.
198
+ */
199
+ async function copyPublicAssets() {
200
+ if (!fsSync.existsSync(PUBLIC_DIR)) return;
201
+ // Copy contents of public into dist, not the public folder itself
202
+ for (const item of await fs.readdir(PUBLIC_DIR)) {
203
+ await fs.cp(path.join(PUBLIC_DIR, item), path.join(DIST_DIR, item), { recursive: true });
204
+ }
205
+ }
134
206
 
135
- line = line.replace('public', '/public')
136
- console.log(line)
137
- }
138
- if (hasImport && line.includes('.css')) {
139
- try {
140
- let isSmallColon = line.includes("'")
141
- let url = isSmallColon ? line.split("'")[1] : line.split('"')[1]
142
- // start from "/" not "/app"
143
- // remvoe all ./ and ../
144
- url = url.replaceAll('./', '/').replaceAll('../', '/')
145
-
146
- let p = path.join(process.cwd(), '/', url)
147
- line = '';
148
- url = url.replace(process.cwd() + '/app', '')
149
- url = url.replace(/\\/g, '/')
150
- if (!bindes.includes(`<link rel="stylesheet" href="${url}">`)) {
151
- bindes.push(`
152
- <style>
153
- ${fs.readFileSync(p, 'utf-8')}
154
- </style>
155
- `)
156
- }
157
- } catch (error) {
158
- console.error(error)
159
- }
160
- }
161
- if (line.toLowerCase().includes('genkey()')) {
162
- line = line.toLowerCase().replace('genkey()', `this.key = "${crypto.randomUUID()}"`)
163
- }
164
- if (!hasImport && line.includes('useFetch')) {
165
- line = line.replace('useFetch', 'this.useFetch')
166
- }
167
- if (!hasImport && line.includes('useState') && line.includes('[')) {
168
- let key = line.split(',')[0].split('[')[1].replace(' ', '')
169
- let b4 = line
170
- b4 = line.replace('useState(', `this.useState('${key}',`)
171
- line = b4
207
+
208
+ async function buildAppEntrypoints(isDev = false) {
209
+ if (!fsSync.existsSync(APP_DIR)) {
210
+ logger.warn("No '/app' directory found, skipping app entrypoint build.");
211
+ return;
212
+ }
213
+
214
+ // Ensure the dist directory exists
215
+ if (!fsSync.existsSync(DIST_DIR)) {
216
+ await fs.mkdir(DIST_DIR, { recursive: true });
217
+ }
218
+
219
+ const devClientScript = isDev ? `
220
+ <script>
221
+ new WebSocket("ws://" + location.host + "/__hmr").onmessage = (msg) => {
222
+ if (msg.data === "reload") location.reload();
223
+ };
224
+ </script>` : "";
225
+
226
+ const entries = fsSync.readdirSync(APP_DIR, { recursive: true })
227
+ .filter(file => /index\.(jsx|tsx)$/.test(file))
228
+ .map(file => ({
229
+ name: path.dirname(file) === '.' ? 'index' : path.dirname(file).replace(/\\/g, '/'),
230
+ path: path.join(APP_DIR, file)
231
+ }));
232
+
233
+ for (const { name, path: entryPath } of entries) {
234
+ // Ensure correct path handling for subdirectories
235
+ const outDir = path.join(DIST_DIR, name === 'index' ? '' : name);
236
+ const outJsPath = path.join(outDir, `index.js`); // Output JavaScript file path
237
+
238
+ // Ensure the output directory exists
239
+ await fs.mkdir(outDir, { recursive: true });
240
+
241
+ // **FIXED CSS HANDLING**: Find, copy, and correctly link CSS files
242
+ const cssLinks = [];
243
+ const cssContent = await fs.readFile(entryPath, "utf8");
244
+ const cssImports = [...cssContent.matchAll(/import\s+['"](.*\.css)['"]/g)];
245
+
246
+ for (const match of cssImports) {
247
+ const cssImportPath = match[1]; // e.g., './styles.css'
248
+ const sourceCssPath = path.resolve(path.dirname(entryPath), cssImportPath);
249
+ if (fsSync.existsSync(sourceCssPath)) {
250
+ const relativeCssPath = path.relative(APP_DIR, sourceCssPath);
251
+ const destCssPath = path.join(DIST_DIR, relativeCssPath);
252
+
253
+ await fs.mkdir(path.dirname(destCssPath), { recursive: true });
254
+ await fs.copyFile(sourceCssPath, destCssPath);
255
+
256
+ const htmlRelativePath = path.relative(outDir, destCssPath).replace(/\\/g, '/');
257
+ cssLinks.push(`<link rel="stylesheet" href="${htmlRelativePath}">`);
258
+ } else {
259
+ logger.warn(`CSS file not found: ${sourceCssPath}`);
172
260
  }
173
- if (!hasImport && line.includes('useAsyncState')) {
174
- let key = line.split(',')[0].split('[')[1].replace(' ', '')
175
- let b4 = line
176
- b4 = line.replace('useAsyncState(', `this.useAsyncState('${key}',`)
177
- line = b4
178
- }
179
- if (!hasImport && line.includes('useEffect')) {
180
- let b4 = line
181
- b4 = line.replace('useEffect(', `this.useEffect(`)
182
- line = b4
183
- }
184
- if (!hasImport && line.includes('useRef')) {
185
- line = line.replace(' ', '')
186
- let b4 = line
187
- let key = line.split('=')[0].split(' ').filter(Boolean)[1]
188
- b4 = line.replace('useRef(', `this.useRef('${key}',`)
189
- line = b4
190
- }
191
-
192
- newLines.push(line)
193
261
  }
194
- let c = newLines.join('\n')
195
- return c
196
- }
197
262
 
198
- if (!fs.existsSync(process.cwd() + '/dev/bundler.js')) {
199
- fs.mkdirSync(process.cwd() + '/dev', { recursive: true })
200
- fs.copyFileSync(require.resolve('vaderjs/bundler/index.js'), process.cwd() + '/dev/bundler.js')
263
+ // Update the script tag to use relative paths for index.js
264
+ const htmlContent = `<!DOCTYPE html>
265
+ <html lang="en">
266
+ <head>
267
+ <meta charset="UTF-8" />
268
+ <title>VaderJS App - ${name}</title>
269
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
270
+ ${cssLinks.join("\n ")}
271
+ ${htmlInjections.join("\n ")}
272
+ </head>
273
+ <body>
274
+ <div id="app"></div>
275
+ <script type="module">
276
+ import App from '/${name}/index.js';
277
+ import * as Vader from '/src/vader/index.js';
278
+ Vader.render(Vader.createElement(App, null), document.getElementById("app"));
279
+ </script>
280
+ ${devClientScript}
281
+ </body>
282
+ </html>`;
283
+
284
+ await fs.writeFile(path.join(outDir, "index.html"), htmlContent);
285
+
286
+ // Log for debugging
287
+
288
+ // Build the JavaScript file and ensure it uses the correct paths
289
+ await build({
290
+ entrypoints: [entryPath],
291
+ outdir: outDir, // Pass the directory path to outdir
292
+ target: "browser",
293
+ minify: false,
294
+ sourcemap: "external",
295
+ external: ["vaderjs"],
296
+ jsxFactory: "e",
297
+ jsxFragment: "Fragment",
298
+ jsxImportSource: "vaderjs",
299
+ });
300
+
301
+ // After build, replace the 'vaderjs' import to the correct path
302
+ let jsContent = await fs.readFile(outJsPath, "utf8");
303
+ jsContent = jsContent.replace(/from\s+['"]vaderjs['"]/g, `from '/src/vader/index.js'`);
304
+ await fs.writeFile(outJsPath, jsContent);
305
+ }
201
306
  }
202
- let start = Date.now()
203
- async function generateApp() {
204
- globalThis.isBuilding = true;
205
- console.log(ansiColors.green('Building...'))
206
- if (mode === 'development') {
207
307
 
208
- } else {
209
- fs.mkdirSync(process.cwd() + '/dist', { recursive: true })
210
- }
211
- try {
212
- let plugins = config.plugins || []
213
- for (let plugin of plugins) {
214
- if (plugin.onBuildStart) {
215
- await plugin.onBuildStart(vader)
216
- }
217
- }
218
- } catch (error) {
219
- console.log(error)
220
- }
308
+
221
309
 
310
+ async function buildAll(isDev = false) {
311
+ logger.info(`Starting VaderJS ${isDev ? 'development' : 'production'} build...`);
312
+ const totalTime = performance.now();
222
313
 
223
- return new Promise(async (resolve, reject) => {
224
- let routes = new Bun.FileSystemRouter({
225
- dir: path.join(process.cwd(), '/app'),
226
- style: 'nextjs'
227
- })
228
- routes.reload()
229
- globalThis.routes = routes.routes
230
- Object.keys(routes.routes).forEach(async (route) => {
231
-
232
- let r = routes.routes[route]
233
- let code = await Bun.file(r).text()
234
- code = handleReplacements(code)
235
- let size = code.length / 1024
236
- r = r.replace(process.cwd().replace(/\\/g, '/') + '/app', '')
237
- r = r.replace('.jsx', '.js').replace('.tsx', '.js')
238
- fs.mkdirSync(path.join(process.cwd() + '/dist', path.dirname(r)), { recursive: true })
239
- let params = routes.match(route).params || {}
240
- let base = routes.match(route)
241
- let paramIndexes = []
242
- for (let param in params) {
243
- let routes = base.pathname.split('/')
244
- let index = routes.indexOf('[' + param + ']')
245
- paramIndexes.push(index)
246
- }
247
-
248
- // dont return
249
- code = await new Bun.Transpiler({
250
- loader: 'tsx',
251
- tsconfig: {
252
- "compilerOptions": {
253
- "jsx": "react",
254
- "jsxFactory": "e",
255
- "jsxFragmentFactory": "Fragment"
256
- }
257
- }
258
- }).transformSync(code)
259
-
260
- fs.writeFileSync(
261
- process.cwd() + '/dist/' + path.dirname(r) + '/' + path.basename(r),
262
- `
263
- let route = window.location.pathname.split('/').filter(Boolean)
264
- let params = {
265
- // get index tehn do route[index]
266
- ${Object.keys(params).map((param, i) => {
267
- if (paramIndexes[i] !== -1) {
268
- var r_copy = r;
269
- r_copy = r_copy.split('/').filter(Boolean)
270
- var index = paramIndexes[i] - 1
271
- return `${param}: route[${index}]`
272
- }
273
- }).join(',\n')}
274
- }
275
-
276
- \n${code}
277
- `
278
- );
279
- fs.mkdirSync(process.cwd() + '/dev', { recursive: true })
280
-
281
-
282
- if (!fs.existsSync(process.cwd() + '/dev/readme.md')) {
283
- fs.writeFileSync(process.cwd() + '/dev/readme.md', `# Please do not edit the bundler.js file in the dev directory. This file is automatically generated by the bundler. \n\n`)
284
- }
285
- async function runT() {
286
- return await new Bun.Transpiler({
287
- loader: 'tsx',
288
- }).transformSync(await Bun.file(require.resolve('vaderjs')).text())
289
- }
290
- if (!fs.existsSync(path.join(process.cwd(), '/dist/src/vader'))
291
- || fs.readFileSync(path.join(process.cwd(), '/dist/src/vader/index.js')) != await runT()
292
- ) {
293
- fs.mkdirSync(process.cwd() + '/dist/src/vader', { recursive: true })
294
- fs.writeFileSync(path.join(process.cwd(), '/dist/src/vader/index.js'), (await runT()))
295
-
296
- }
297
- await Bun.spawn({
298
- cmd: [bunPath, 'run', './dev/bundler.js'],
299
- cwd: process.cwd(),
300
- stdout: 'inherit',
301
- env: {
302
- ENTRYPOINT: path.join(process.cwd(), 'dist', path.dirname(r), path.basename(r)),
303
- ROOT: path.join(process.cwd(), 'app/'),
304
- OUT: path.dirname(r),
305
- file: path.join(process.cwd(), 'dist', path.dirname(r), path.basename(r)),
306
- DEV: mode === 'development',
307
- size,
308
- bindes: bindes.join('\n'),
309
- filePath: r,
310
- isAppFile: true,
311
- isJsx: true,
312
- INPUT: `../app/${r.replace('.js', '.jsx').replace('.tsx', '.js')}`,
313
- },
314
- onExit({ exitCode: code }) {
315
- if (code === 0) {
316
- bindes = [];
317
- resolve();
318
- } else {
319
- reject();
320
- }
321
- },
322
- });
323
- })
324
-
325
- switch (host_provider) {
326
- case 'vercel':
327
-
328
- let vercelData = {
329
- rewrites: []
330
- }
331
-
332
- for (let route in routes.routes) {
333
- let { filePath, kind, name, params, pathname, query } = routes.match(route)
334
- let r = route
335
-
336
- if (r.includes('[')) {
337
- r = r.replaceAll('[', ':').replaceAll(']', '')
338
- }
339
- if (r === '/') {
340
- continue
341
- }
342
-
343
- vercelData.rewrites.push({
344
- source: r,
345
- destination: `${path.dirname(routes.routes[route]).replace(process.cwd().replace(/\\/g, '/') + '/app', '')}/index.html`
346
- })
347
- }
348
-
349
- fs.writeFileSync(process.cwd() + '/vercel.json', JSON.stringify(vercelData, null, 4))
350
- break;
351
- case 'apache':
352
- let data = ''
314
+ htmlInjections = [];
353
315
 
354
- }
355
- // run all plugins that have onBuildFinish
356
- try {
357
- let plugins = config.plugins || []
358
- for (let plugin of plugins) {
359
- if (plugin.onBuildFinish) {
360
- await plugin.onBuildFinish(vader)
361
- }
362
- }
363
- } catch (error) {
364
- console.error(ansiColors.red(error))
365
- }
316
+ // Ensure dist directory exists before cleaning
317
+ if (fsSync.existsSync(DIST_DIR)) {
318
+ await fs.rm(DIST_DIR, { recursive: true, force: true });
319
+ }
366
320
 
367
- })
321
+ // Create the dist directory if it doesn't exist
322
+ await fs.mkdir(DIST_DIR, { recursive: true });
368
323
 
324
+ await runPluginHook("onBuildStart");
369
325
 
370
- }
326
+ // Build the components in steps and handle errors properly
327
+ await timedStep("Building VaderJS Core", buildVaderCore);
328
+ await timedStep("Building App Source (/src)", buildSrc);
329
+ await timedStep("Copying Public Assets", copyPublicAssets);
330
+ await timedStep("Building App Entrypoints (/app)", () => buildAppEntrypoints(isDev));
371
331
 
372
- function handleFiles() {
373
- return new Promise(async (resolve, reject) => {
374
- try {
375
- let glob = new Glob('public/**/*')
376
- for await (var i of glob.scan()) {
377
- let file = i
378
- fs.mkdirSync(path.join(process.cwd() + '/dist', path.dirname(file)), { recursive: true })
379
- if (fs.existsSync(path.join(process.cwd() + '/dist', file))) {
380
- fs.rmSync(path.join(process.cwd() + '/dist', file))
381
- }
382
- fs.copyFileSync(file, path.join(process.cwd() + '/dist', file))
383
- }
384
- let glob2 = new Glob('src/**/*')
385
- for await (var i of glob2.scan()) {
386
- var file = i
387
- fs.mkdirSync(path.join(process.cwd() + '/dist', path.dirname(file)), { recursive: true })
388
- // turn jsx to js
389
- if (file.includes('.jsx') || file.includes('.tsx')) {
390
- let code = await Bun.file(file).text()
391
-
392
- code = handleReplacements(code)
393
- code = await new Bun.Transpiler({
394
- loader: 'tsx',
395
- }).transformSync(code)
396
-
397
- file = file.replace('.jsx', '.js').replace('.tsx', '.js')
398
- fs.writeFileSync(path.join(process.cwd() + '/dist', file.replace('.jsx', '.js').replace('.tsx', '.js')), code)
399
- await Bun.spawn({
400
- cmd: [bunPath, 'run', './dev/bundler.js'],
401
- cwd: process.cwd(),
402
- stdout: 'inherit',
403
- env: {
404
- ENTRYPOINT: path.join(process.cwd() + '/dist/' + file.replace('.jsx', '.js').replace('.tsx', '.js')),
405
- ROOT: process.cwd() + '/app/',
406
- OUT: path.dirname(file),
407
- shouldReplace: true,
408
- file: process.cwd() + '/dist/' + file.replace('.jsx', '.js').replace('.tsx', '.js'),
409
- DEV: mode === 'development',
410
- size: code.length / 1024,
411
- filePath: file.replace('.jsx', '.js'),
412
- isTs: file.includes('.tsx'),
413
- INPUT: path.join(process.cwd(), file.replace('.js', '.jsx').replace('.tsx', '.js')),
414
- },
415
- onExit({ exitCode: code }) {
416
- if (code === 0) {
417
- resolve()
418
- } else {
419
- reject()
420
- }
421
- }
422
- })
423
- } else if (file.includes('.ts')) {
424
- let code = await Bun.file(file).text()
425
- code = handleReplacements(code)
426
- file = file.replace('.ts', '.js')
427
- fs.writeFileSync(path.join(process.cwd() + '/dist', file.replace('.ts', '.js')), code)
428
- await Bun.spawn({
429
- cmd: [bunPath, 'run', './dev/bundler.js'],
430
- cwd: process.cwd(),
431
- stdout: 'inherit',
432
- env: {
433
- ENTRYPOINT: path.join(process.cwd() + '/dist/' + file.replace('.ts', '.js')),
434
- ROOT: process.cwd() + '/app/',
435
- OUT: path.dirname(file),
436
- file: process.cwd() + '/dist/' + file.replace('.ts', '.js'),
437
- DEV: mode === 'development',
438
- isTS: true,
439
- size: code.length / 1024,
440
- filePath: file.replace('.ts', '.js'),
441
- INPUT: path.join(process.cwd(), file.replace('.js', '.jsx')),
442
- },
443
- onExit({ exitCode: code }) {
444
- if (code === 0) {
445
- resolve()
446
- } else {
447
- reject()
448
- }
449
- }
450
- })
451
- }
452
-
453
- }
454
-
455
- resolve()
456
- } catch (error) {
457
- reject(error)
458
- }
459
- })
332
+ await runPluginHook("onBuildFinish");
333
+
334
+ // Calculate the total duration and log it
335
+ const duration = (performance.now() - totalTime).toFixed(2);
336
+ logger.success(`Total build finished in ${duration}ms. Output is in /dist.`);
460
337
  }
461
- globalThis.clients = []
462
338
 
463
- if (mode === 'development') {
339
+ async function runDevServer() {
340
+ await buildAll(true);
341
+
342
+ const clients = new Set();
343
+ const port = config.port || 3000;
344
+
345
+ logger.info(`Starting dev server at http://localhost:${port}`);
346
+
347
+ serve({
348
+ port,
349
+ fetch(req, server) {
350
+ const url = new URL(req.url);
351
+ if (url.pathname === "/__hmr" && server.upgrade(req)) {
352
+ return;
353
+ }
354
+ let filePath = path.join(DIST_DIR, url.pathname);
355
+ if (!path.extname(filePath)) {
356
+ filePath = path.join(filePath, "index.html");
357
+ }
358
+ const file = Bun.file(filePath);
359
+ return file.exists().then(exists =>
360
+ exists ? new Response(file) : new Response("Not Found", { status: 404 })
361
+ );
362
+ },
363
+ websocket: {
364
+ open: (ws) => clients.add(ws),
365
+ close: (ws) => clients.delete(ws),
366
+ },
367
+ });
368
+
369
+ const debouncedBuild = debounce(async () => {
464
370
  try {
465
- await generateApp()
466
- await handleFiles()
467
- let watcher;
468
- let isBuilding = false;
469
- let debounceTimeout;
470
-
471
- const startWatcher = () => {
472
- if (watcher) watcher.close(); // Close any existing watcher
473
-
474
- watcher = fs.watch(path.join(process.cwd(), '/'), { recursive: true }, (eventType, file) => {
475
- if (!file) return; // Ensure file name is valid
476
- if (file.includes('node_modules')) return;
477
- if (file.includes('dist')) return;
478
- if (!fs.existsSync(path.join(process.cwd(), file))) {
479
- fs.rmSync(path.join(process.cwd(), "dist", file))
480
- }
481
-
482
- if (
483
- file.endsWith('.tsx') || file.endsWith('.jsx') || file.endsWith('.css') || file.endsWith('.ts')
484
- ) {
485
- // Reset config if needed
486
- if (file.endsWith('vader.config.ts')) {
487
- delete require.cache[require.resolve(process.cwd() + '/vader.config.ts')];
488
- globalThis.config = require(process.cwd() + '/vader.config.ts').default;
489
- }
490
-
491
- clearTimeout(debounceTimeout);
492
- debounceTimeout = setTimeout(async () => {
493
- if (!isBuilding) {
494
- isBuilding = true;
495
- try {
496
- await generateApp();
497
- await handleFiles();
498
- setTimeout(() => {
499
- clients.forEach(c => c.send('reload'));
500
- }, 1000);
501
- } catch (error) {
502
- console.error(error);
503
- } finally {
504
- isBuilding = false;
505
- }
506
- }
507
- }, 500);
508
- }
509
-
510
- // Restart watcher if a new directory is created
511
- if (eventType === 'rename') {
512
- setTimeout(startWatcher, 500); // Slight delay to allow the OS to recognize new files
513
- }
514
- });
515
- };
516
-
517
- // Start the watcher and restart it periodically
518
- setInterval(startWatcher, 500);
519
- startWatcher(); // Run initially
520
- } catch (error) {
521
- console.error(error)
371
+ await buildAll(true);
372
+ for (const client of clients) {
373
+ client.send("reload");
374
+ }
375
+ } catch (e) {
376
+ logger.error("Rebuild failed:", e);
522
377
  }
378
+ }, 200);
523
379
 
380
+ const watchDirs = [APP_DIR, SRC_DIR, PUBLIC_DIR].filter(fsSync.existsSync);
381
+ for (const dir of watchDirs) {
382
+ fsSync.watch(dir, { recursive: true }, debouncedBuild);
383
+ }
524
384
  }
525
- else if (mode == 'production') {
526
- await handleFiles()
527
- await generateApp()
528
385
 
529
- console.log(`Build complete in ${Date.now() - start}ms at ${new Date().toLocaleTimeString()}`);
386
+ async function runProdServer() {
387
+ const port = config.port || 3000;
388
+ logger.info(`Serving production build from /dist on http://localhost:${port}`);
389
+ serve({
390
+ port,
391
+ fetch(req) {
392
+ const url = new URL(req.url);
393
+ let filePath = path.join(DIST_DIR, url.pathname);
394
+ if (!path.extname(filePath)) {
395
+ filePath = path.join(filePath, "index.html");
396
+ }
397
+ const file = Bun.file(filePath);
398
+ return file.exists().then(exists =>
399
+ exists ? new Response(file) : new Response("Not Found", { status: 404 })
400
+ );
401
+ },
402
+ });
530
403
  }
531
- else {
532
- if (isBuilding) console.log(`Build complete in ${Date.now() - start}ms at ${new Date().toLocaleTimeString()}`);
533
404
 
405
+ function debounce(fn, delay) {
406
+ let timeoutId;
407
+ return (...args) => {
408
+ clearTimeout(timeoutId);
409
+ timeoutId = setTimeout(() => fn(...args), delay);
410
+ };
534
411
  }
535
412
 
536
- if (mode == 'development' || mode == 'serve') {
537
- let server = Bun.serve({
538
- port: port || 8080,
539
- websocket: {
540
- open(ws) {
541
- globalThis.clients.push(ws)
542
- ws.send('Connected')
543
- },
544
- message(ws, message) {
545
- globalThis.clients.forEach(c => {
546
- c.send(message)
547
- })
548
- },
549
-
550
- },
551
- async fetch(req, res) {
552
- if (res.upgrade(req)) {
553
- return new Response('Upgraded', { status: 101 })
554
- }
555
-
556
- let url = new URL(req.url)
557
- if (url.pathname.includes('.')) {
558
- let p = url.pathname.replaceAll("%5B", "[").replaceAll("%5D", "]")
559
- let file = await Bun.file(path.join(process.cwd() + '/dist' + p))
560
- if (!await file.exists()) return new Response('Not found', { status: 404 })
561
- let imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp', 'image/tiff', 'image/bmp', 'image/ico', 'image/cur', 'image/jxr', 'image/jpg']
562
-
563
- return new Response(imageTypes.includes(file.type) ? await file.arrayBuffer() : await file.text(), {
564
- headers: {
565
- 'Content-Type': file.type,
566
- 'Cache-Control': imageTypes.includes(file.type) ? 'max-age=31536000' : 'no-cache',
567
- 'Access-Control-Allow-Origin': '*'
568
- }
569
- })
570
- }
571
- let router = new Bun.FileSystemRouter({
572
- dir: process.cwd() + '/app',
573
- style: 'nextjs'
574
- })
575
- router.reload()
576
- let route = router.match(url.pathname)
577
- if (!route) {
578
- return new Response('Not found', { status: 404 })
579
- }
580
- let p = route.pathname;
581
- let base = path.dirname(route.filePath)
582
- base = base.replace(/\\/g, '/')
583
- base = base.replace(path.join(process.cwd() + '/app').replace(/\\/g, '/'), '')
584
- base = base.replace(/\\/g, '/').replace('/app', '/dist')
585
- base = process.cwd() + "/dist/" + base
586
- if (!fs.existsSync(path.join(base, 'index.html'))) {
587
- return new Response(`
588
- <html>
589
- <head>
590
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
591
- <meta http-equiv="refresh" content="5">
592
- </head>
593
- <body>
594
- <p>Rerouting to display changes from server</p>
595
- </body>
596
- `, {
597
- headers: {
598
- 'Content-Type': 'text/html',
599
- 'Cache-Control': 'no-cache'
600
- }
601
- })
602
- }
603
- let data = await Bun.file(path.join(base, 'index.html')).text()
604
- if (mode == "development") {
605
- return new Response(data + `
606
- <script>
607
- let ws = new WebSocket(\`\${location.protocol === 'https:' ? 'wss' : 'ws'}://\${location.host}\`)
608
- ws.onmessage = (e) => {
609
- if(e.data === 'reload'){
610
- console.log('Reloading to display changes from server')
611
- window.location.reload()
612
- }
613
- }
614
- ws.onopen = () => {
615
- console.log('Connected to hmr server')
616
- }
617
-
618
- ws.onclose = () => {
619
- // try to reconnect
620
- console.log('Reconnecting to hmr server')
621
- ws = new WebSocket(\`\${location.protocol === 'https:' ? 'wss' : 'ws'}://\${location.host}\`)
622
- }
623
-
624
- </script>
625
- `, {
626
- headers: {
627
- 'Content-Type': 'text/html'
628
- }
629
- })
630
- } else {
631
- return new Response(data, {
632
- headers: {
633
- 'Content-Type': 'text/html'
634
- }
635
- })
636
- }
637
-
638
- }
639
- })
413
+ // --- SCRIPT ENTRYPOINT ---
414
+
415
+ async function main() {
416
+ const banner = `${colors.magenta}
417
+ __ __ ____ ____ _______ __
418
+ | | / |/ __ \ / __ \ / ____/ |/ /
419
+ | | / / / / // /_/ // /___ | /
420
+ | | / / /_/ / \____// /___ / |
421
+ |____/____/_____/ /_____/ |_| |_|
422
+ ${colors.reset}`;
423
+
424
+ console.log(banner);
425
+
426
+
427
+ config = await loadConfig();
428
+ config.port = config.port || 3000;
429
+
430
+ const command = process.argv[2];
431
+
432
+ if (command === "dev") {
433
+ await runDevServer();
434
+ } else if (command === "build") {
435
+ await buildAll(false);
436
+ } else if (command === "serve") {
437
+ await buildAll(false);
438
+ await runProdServer();
439
+ }
440
+ else if(command === "init"){
441
+ init().catch((e) => {
442
+ console.error("Initialization failed:", e);
443
+ process.exit(1);
444
+ });
445
+
446
+ } else {
447
+ logger.error(`Unknown command: '${command}'.`);
448
+ logger.info("Available commands: 'dev', 'build', 'serve'");
449
+ process.exit(1);
450
+ }
451
+ }
640
452
 
641
- console.log(ansiColors.green('Server started at http://localhost:' + port || 8080))
642
- }
453
+ main().catch(err => {
454
+ logger.error("An unexpected error occurred:", err);
455
+ process.exit(1);
456
+ });