vaderjs 2.3.15 → 2.3.17
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/config/index.ts +2 -2
- package/main.ts +215 -6
- package/package.json +1 -1
package/config/index.ts
CHANGED
package/main.ts
CHANGED
|
@@ -60,6 +60,159 @@ const TEMP_SRC_DIR = path.join(PROJECT_ROOT, ".vader_temp_src");
|
|
|
60
60
|
let config: any = {};
|
|
61
61
|
let htmlInjections: string[] = [];
|
|
62
62
|
|
|
63
|
+
// --- Plugin Support ---
|
|
64
|
+
|
|
65
|
+
interface Plugin {
|
|
66
|
+
name: string;
|
|
67
|
+
version: string;
|
|
68
|
+
description?: string;
|
|
69
|
+
onBuildStart?: (api: PluginAPI) => Promise<void> | void;
|
|
70
|
+
onBuildFinish?: (api: PluginAPI) => Promise<void> | void;
|
|
71
|
+
onFileChange?: (file: string, api: PluginAPI) => Promise<void> | void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface PluginAPI {
|
|
75
|
+
injectHTML(html: string): void;
|
|
76
|
+
addWatchPath(path: string): void;
|
|
77
|
+
config: any;
|
|
78
|
+
isDev: boolean;
|
|
79
|
+
distDir: string;
|
|
80
|
+
srcDir: string;
|
|
81
|
+
publicDir: string;
|
|
82
|
+
projectRoot: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let plugins: Plugin[] = [];
|
|
86
|
+
|
|
87
|
+
// Create plugin API helper
|
|
88
|
+
function createPluginAPI(): PluginAPI {
|
|
89
|
+
return {
|
|
90
|
+
injectHTML: (html: string) => {
|
|
91
|
+
htmlInjections.push(html);
|
|
92
|
+
},
|
|
93
|
+
addWatchPath: (watchPath: string) => {
|
|
94
|
+
if (fsSync.existsSync(watchPath)) {
|
|
95
|
+
watcher.watch(watchPath);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
config: config,
|
|
99
|
+
isDev: globalThis.isDev,
|
|
100
|
+
distDir: DIST_DIR,
|
|
101
|
+
srcDir: SRC_DIR,
|
|
102
|
+
publicDir: PUBLIC_DIR,
|
|
103
|
+
projectRoot: PROJECT_ROOT
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Run plugin hooks
|
|
108
|
+
async function runPluginHook(hookName: 'onBuildStart' | 'onBuildFinish', api: PluginAPI) {
|
|
109
|
+
for (const plugin of plugins) {
|
|
110
|
+
if (plugin[hookName]) {
|
|
111
|
+
try {
|
|
112
|
+
logger.info(`Running plugin hook: ${plugin.name} - ${hookName}`);
|
|
113
|
+
await plugin[hookName]!(api);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
logger.error(`Error in plugin "${plugin.name}" during ${hookName}:`, e);
|
|
116
|
+
if (!globalThis.isDev) process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Load plugins from config
|
|
123
|
+
async function loadPluginsFromConfig() {
|
|
124
|
+
console.log(config)
|
|
125
|
+
if (!config.plugins || !Array.isArray(config.plugins)) {
|
|
126
|
+
logger.info("No plugins defined in config");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const loadedPlugins: Plugin[] = [];
|
|
131
|
+
|
|
132
|
+
for (const pluginConfig of config.plugins) {
|
|
133
|
+
try {
|
|
134
|
+
let plugin;
|
|
135
|
+
|
|
136
|
+
// If plugin is a string, import it
|
|
137
|
+
if (typeof pluginConfig === 'string') {
|
|
138
|
+
const pluginPath = path.isAbsolute(pluginConfig)
|
|
139
|
+
? pluginConfig
|
|
140
|
+
: path.join(PROJECT_ROOT, pluginConfig);
|
|
141
|
+
|
|
142
|
+
// Check if file exists
|
|
143
|
+
if (fsSync.existsSync(pluginPath)) {
|
|
144
|
+
const pluginModule = await import(pluginPath);
|
|
145
|
+
plugin = pluginModule.default || pluginModule;
|
|
146
|
+
} else {
|
|
147
|
+
// Try as npm package
|
|
148
|
+
plugin = await import(pluginConfig);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// If plugin is an object with resolve property
|
|
152
|
+
else if (typeof pluginConfig === 'object' && pluginConfig.resolve) {
|
|
153
|
+
const pluginModule = await import(pluginConfig.resolve);
|
|
154
|
+
plugin = pluginModule.default || pluginModule;
|
|
155
|
+
|
|
156
|
+
// Pass options to plugin if it's a factory function
|
|
157
|
+
if (typeof plugin === 'function' && pluginConfig.options) {
|
|
158
|
+
plugin = await plugin(pluginConfig.options);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// If plugin is already a plugin object
|
|
162
|
+
else if (typeof pluginConfig === 'object' && pluginConfig.name) {
|
|
163
|
+
plugin = pluginConfig;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (plugin && typeof plugin === 'object' && plugin.name) {
|
|
167
|
+
loadedPlugins.push(plugin);
|
|
168
|
+
logger.success(`Loaded plugin: ${plugin.name} v${plugin.version || 'unknown'}`);
|
|
169
|
+
} else {
|
|
170
|
+
logger.warn(`Invalid plugin: missing name property`);
|
|
171
|
+
}
|
|
172
|
+
} catch (e) {
|
|
173
|
+
logger.error(`Failed to load plugin:`, e);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
plugins = loadedPlugins;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Also load from plugins directory (backward compatibility)
|
|
181
|
+
async function loadPluginsFromDirectory() {
|
|
182
|
+
const pluginsDir = path.join(PROJECT_ROOT, "plugins");
|
|
183
|
+
|
|
184
|
+
if (!fsSync.existsSync(pluginsDir)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const pluginFiles = await fs.readdir(pluginsDir);
|
|
189
|
+
const loadedPlugins: Plugin[] = [];
|
|
190
|
+
|
|
191
|
+
for (const file of pluginFiles) {
|
|
192
|
+
if (file.endsWith('.js') || file.endsWith('.ts')) {
|
|
193
|
+
try {
|
|
194
|
+
const pluginPath = path.join(pluginsDir, file);
|
|
195
|
+
const pluginModule = await import(pluginPath);
|
|
196
|
+
const plugin = pluginModule.default || pluginModule;
|
|
197
|
+
|
|
198
|
+
if (plugin && typeof plugin === 'object' && plugin.name) {
|
|
199
|
+
// Check if plugin already loaded from config
|
|
200
|
+
if (!plugins.some(p => p.name === plugin.name)) {
|
|
201
|
+
loadedPlugins.push(plugin);
|
|
202
|
+
logger.info(`Loaded plugin from directory: ${plugin.name} v${plugin.version}`);
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
logger.warn(`Invalid plugin in ${file}: missing name property`);
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {
|
|
208
|
+
logger.error(`Failed to load plugin ${file}:`, e);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
plugins = [...plugins, ...loadedPlugins];
|
|
214
|
+
}
|
|
215
|
+
|
|
63
216
|
// --- JSConfig Setup ---
|
|
64
217
|
|
|
65
218
|
async function ensureJSConfig() {
|
|
@@ -142,9 +295,14 @@ const watcher = new FileWatcher();
|
|
|
142
295
|
|
|
143
296
|
export async function loadConfig() {
|
|
144
297
|
try {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
298
|
+
const configPath = path.join(PROJECT_ROOT, "vaderjs.config.ts");
|
|
299
|
+
if (fsSync.existsSync(configPath)) {
|
|
300
|
+
const mod = await import(configPath);
|
|
301
|
+
return mod.default || mod;
|
|
302
|
+
}
|
|
303
|
+
return {};
|
|
304
|
+
} catch (error) {
|
|
305
|
+
logger.warn("Failed to load config, using defaults:", error);
|
|
148
306
|
return {};
|
|
149
307
|
}
|
|
150
308
|
}
|
|
@@ -276,6 +434,9 @@ async function buildAppEntrypoints() {
|
|
|
276
434
|
// First check for root App file (VaderJS standard)
|
|
277
435
|
const appFile = findAppFile();
|
|
278
436
|
|
|
437
|
+
// Build HTML with injections from plugins
|
|
438
|
+
const htmlInjectionsString = htmlInjections.join('\n ');
|
|
439
|
+
|
|
279
440
|
if (appFile) {
|
|
280
441
|
logger.info(`Building App from: ${appFile}`);
|
|
281
442
|
|
|
@@ -295,7 +456,8 @@ async function buildAppEntrypoints() {
|
|
|
295
456
|
<head>
|
|
296
457
|
<meta charset="UTF-8"/>
|
|
297
458
|
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
298
|
-
<title
|
|
459
|
+
<title>${config.title || 'Vader App'}</title>
|
|
460
|
+
${htmlInjectionsString}
|
|
299
461
|
</head>
|
|
300
462
|
<body>
|
|
301
463
|
<div id="app"></div>
|
|
@@ -344,7 +506,8 @@ async function buildAppEntrypoints() {
|
|
|
344
506
|
<head>
|
|
345
507
|
<meta charset="UTF-8"/>
|
|
346
508
|
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
347
|
-
<title
|
|
509
|
+
<title>${config.title || 'Vader App'}</title>
|
|
510
|
+
${htmlInjectionsString}
|
|
348
511
|
</head>
|
|
349
512
|
<body>
|
|
350
513
|
<div id="app"></div>
|
|
@@ -359,6 +522,21 @@ async function buildAppEntrypoints() {
|
|
|
359
522
|
async function buildAll(dev = false) {
|
|
360
523
|
const start = performance.now();
|
|
361
524
|
|
|
525
|
+
// Reset HTML injections before build
|
|
526
|
+
htmlInjections = [];
|
|
527
|
+
|
|
528
|
+
// Load plugins from config first
|
|
529
|
+
await loadPluginsFromConfig();
|
|
530
|
+
|
|
531
|
+
// Also load from plugins directory (backward compatibility)
|
|
532
|
+
await loadPluginsFromDirectory();
|
|
533
|
+
|
|
534
|
+
// Create plugin API
|
|
535
|
+
const pluginAPI = createPluginAPI();
|
|
536
|
+
|
|
537
|
+
// Run onBuildStart hooks
|
|
538
|
+
await runPluginHook('onBuildStart', pluginAPI);
|
|
539
|
+
|
|
362
540
|
// Ensure jsconfig.json exists before building
|
|
363
541
|
await ensureJSConfig();
|
|
364
542
|
|
|
@@ -373,6 +551,9 @@ async function buildAll(dev = false) {
|
|
|
373
551
|
await timedStep("Copy Public", copyPublicAssets);
|
|
374
552
|
await timedStep("Build App", buildAppEntrypoints);
|
|
375
553
|
|
|
554
|
+
// Run onBuildFinish hooks
|
|
555
|
+
await runPluginHook('onBuildFinish', pluginAPI);
|
|
556
|
+
|
|
376
557
|
logger.success(
|
|
377
558
|
`Build finished in ${(performance.now() - start).toFixed(1)}ms`
|
|
378
559
|
);
|
|
@@ -431,14 +612,42 @@ async function runDevServer() {
|
|
|
431
612
|
watcher.watch(SRC_DIR);
|
|
432
613
|
watcher.watch(PUBLIC_DIR);
|
|
433
614
|
|
|
615
|
+
// Watch config file
|
|
616
|
+
watcher.watch(path.join(PROJECT_ROOT, "vaderjs.config.js"));
|
|
617
|
+
|
|
618
|
+
// Watch plugins directory
|
|
619
|
+
const pluginsDir = path.join(PROJECT_ROOT, "plugins");
|
|
620
|
+
if (fsSync.existsSync(pluginsDir)) {
|
|
621
|
+
watcher.watch(pluginsDir);
|
|
622
|
+
}
|
|
623
|
+
|
|
434
624
|
// Also watch for root App file
|
|
435
625
|
const rootAppDir = path.dirname(findAppFile() || "");
|
|
436
626
|
if (rootAppDir && rootAppDir !== PROJECT_ROOT) {
|
|
437
627
|
watcher.watch(rootAppDir);
|
|
438
628
|
}
|
|
439
629
|
|
|
440
|
-
watcher.onChange(async () => {
|
|
630
|
+
watcher.onChange(async (file) => {
|
|
441
631
|
logger.info("Changes detected, rebuilding...");
|
|
632
|
+
|
|
633
|
+
// If config or plugin file changed, reload config and plugins
|
|
634
|
+
if (file.includes('vaderjs.config.js') || file.includes('plugins')) {
|
|
635
|
+
logger.info("Config or plugin changed, reloading...");
|
|
636
|
+
config = await loadConfig();
|
|
637
|
+
htmlInjections = [];
|
|
638
|
+
plugins = [];
|
|
639
|
+
await loadPluginsFromConfig();
|
|
640
|
+
await loadPluginsFromDirectory();
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Run onFileChange hooks for plugins
|
|
644
|
+
const pluginAPI = createPluginAPI();
|
|
645
|
+
for (const plugin of plugins) {
|
|
646
|
+
if (plugin.onFileChange) {
|
|
647
|
+
await plugin.onFileChange(file, pluginAPI);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
442
651
|
await buildAll(true);
|
|
443
652
|
|
|
444
653
|
for (const c of clients) c.send("reload");
|