@buzola/vite-plugin 0.0.8 → 0.0.10
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/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +39 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +20 -0
- package/dist/config.js.map +1 -0
- package/dist/generate.d.ts +26 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +44 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +23 -33
- package/dist/plugin.js.map +1 -1
- package/package.json +5 -2
- package/src/cli.ts +43 -0
- package/src/config.ts +35 -0
- package/src/generate.ts +64 -0
- package/src/index.ts +6 -0
- package/src/plugin.ts +25 -43
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
4
|
+
import { loadConfig } from './config.js';
|
|
5
|
+
import { generate } from './generate.js';
|
|
6
|
+
const { values } = parseArgs({
|
|
7
|
+
options: {
|
|
8
|
+
'routes-dir': { type: 'string', short: 'r' },
|
|
9
|
+
output: { type: 'string', short: 'o' },
|
|
10
|
+
'persistent-param': { type: 'string', multiple: true },
|
|
11
|
+
root: { type: 'string' },
|
|
12
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
13
|
+
},
|
|
14
|
+
strict: true,
|
|
15
|
+
});
|
|
16
|
+
if (values.help) {
|
|
17
|
+
console.log(`Usage: buzola-generate [options]
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
-r, --routes-dir <path> Routes directory (default: src/routes)
|
|
21
|
+
-o, --output <path> Output file path (default: src/buzola.gen.ts)
|
|
22
|
+
--persistent-param <name> Persistent parameter name (repeatable)
|
|
23
|
+
--root <path> Project root directory (default: cwd)
|
|
24
|
+
-h, --help Show this help message`);
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
const root = path.resolve(values.root ?? process.cwd());
|
|
28
|
+
const fileConfig = await loadConfig(root);
|
|
29
|
+
const routesDir = path.resolve(root, values['routes-dir'] ?? fileConfig.routesDir ?? 'src/routes');
|
|
30
|
+
const outputPath = path.resolve(root, values.output ?? fileConfig.output ?? 'src/buzola.gen.ts');
|
|
31
|
+
const persistentParams = values['persistent-param'] ?? fileConfig.persistentParams;
|
|
32
|
+
const changed = await generate({ routesDir, outputPath, persistentParams });
|
|
33
|
+
if (changed) {
|
|
34
|
+
console.log(`Generated ${path.relative(root, outputPath)}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.log('No changes');
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC5B,OAAO,EAAE;QACR,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;QAC5C,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;QACtC,kBAAkB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;QACtD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;KACrD;IACD,MAAM,EAAE,IAAI;CACZ,CAAC,CAAA;AAEF,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IACjB,OAAO,CAAC,GAAG,CAAC;;;;;;;yDAO4C,CAAC,CAAA;IACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AAChB,CAAC;AAED,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;AACvD,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAA;AAEzC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,YAAY,CAAC,IAAI,UAAU,CAAC,SAAS,IAAI,YAAY,CAAC,CAAA;AAClG,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,IAAI,UAAU,CAAC,MAAM,IAAI,mBAAmB,CAAC,CAAA;AAChG,MAAM,gBAAgB,GAAG,MAAM,CAAC,kBAAkB,CAAC,IAAI,UAAU,CAAC,gBAAgB,CAAA;AAElF,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC,CAAA;AAE3E,IAAI,OAAO,EAAE,CAAC;IACb,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,CAAC,CAAA;AAC5D,CAAC;KAAM,CAAC;IACP,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;AAC1B,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface BuzolaConfig {
|
|
2
|
+
/** Path to routes directory, relative to project root. Defaults to "src/routes". */
|
|
3
|
+
routesDir?: string;
|
|
4
|
+
/** Path to output generated file, relative to project root. Defaults to "src/buzola.gen.ts". */
|
|
5
|
+
output?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Parameter names that should be persistent across navigations.
|
|
8
|
+
* When set, generates a BuzolaPersistentParams type augmentation
|
|
9
|
+
* so these params become optional in Link and navigate() calls.
|
|
10
|
+
*/
|
|
11
|
+
persistentParams?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Load buzola config from the project root.
|
|
15
|
+
* Looks for `buzola.config.ts`, `buzola.config.js`, or `buzola.config.mjs`.
|
|
16
|
+
*
|
|
17
|
+
* @returns The loaded config, or an empty object if no config file is found.
|
|
18
|
+
*/
|
|
19
|
+
export declare function loadConfig(root: string): Promise<BuzolaConfig>;
|
|
20
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC5B,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gGAAgG;IAChG,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAID;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAUpE"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
const CONFIG_FILES = ['buzola.config.ts', 'buzola.config.js', 'buzola.config.mjs'];
|
|
4
|
+
/**
|
|
5
|
+
* Load buzola config from the project root.
|
|
6
|
+
* Looks for `buzola.config.ts`, `buzola.config.js`, or `buzola.config.mjs`.
|
|
7
|
+
*
|
|
8
|
+
* @returns The loaded config, or an empty object if no config file is found.
|
|
9
|
+
*/
|
|
10
|
+
export async function loadConfig(root) {
|
|
11
|
+
for (const file of CONFIG_FILES) {
|
|
12
|
+
const configPath = path.resolve(root, file);
|
|
13
|
+
if (!fs.existsSync(configPath))
|
|
14
|
+
continue;
|
|
15
|
+
const mod = await import(configPath);
|
|
16
|
+
return mod.default ?? mod;
|
|
17
|
+
}
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAejC,MAAM,YAAY,GAAG,CAAC,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,CAAC,CAAA;AAElF;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC5C,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QACjC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,SAAQ;QAExC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAA;QACpC,OAAO,GAAG,CAAC,OAAO,IAAI,GAAG,CAAA;IAC1B,CAAC;IAED,OAAO,EAAE,CAAA;AACV,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ModuleLoader } from './generator/page-extractor.js';
|
|
2
|
+
export interface GenerateOptions {
|
|
3
|
+
/** Absolute path to the routes directory. */
|
|
4
|
+
routesDir: string;
|
|
5
|
+
/** Absolute path to the output file. */
|
|
6
|
+
outputPath: string;
|
|
7
|
+
/** Parameter names that should be persistent across navigations. */
|
|
8
|
+
persistentParams?: string[];
|
|
9
|
+
/** Custom module loader. Defaults to native `import()`. */
|
|
10
|
+
moduleLoader?: ModuleLoader;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate the `buzola.gen.ts` route module from a routes directory.
|
|
14
|
+
*
|
|
15
|
+
* Works standalone (no Vite required) — uses native `import()` as the
|
|
16
|
+
* default module loader. Pass a custom `moduleLoader` to integrate with
|
|
17
|
+
* Vite's `ssrLoadModule` or other bundlers.
|
|
18
|
+
*
|
|
19
|
+
* Note: The default module loader uses native `import()`, which can load
|
|
20
|
+
* `.ts`/`.tsx` files with Bun but not with Node.js. For Node.js, provide
|
|
21
|
+
* a custom `moduleLoader` (e.g. via Vite's `ssrLoadModule`).
|
|
22
|
+
*
|
|
23
|
+
* @returns `true` if the output file was written (content changed), `false` otherwise.
|
|
24
|
+
*/
|
|
25
|
+
export declare function generate(options: GenerateOptions): Promise<boolean>;
|
|
26
|
+
//# sourceMappingURL=generate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAIjE,MAAM,WAAW,eAAe;IAC/B,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAA;IACjB,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B,2DAA2D;IAC3D,YAAY,CAAC,EAAE,YAAY,CAAA;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAYzE"}
|
package/dist/generate.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { generateRouteModule } from './generator/codegen.js';
|
|
3
|
+
import { scanRouteFiles } from './generator/scanner.js';
|
|
4
|
+
import { buildFileRouteTree } from './generator/tree-builder.js';
|
|
5
|
+
/**
|
|
6
|
+
* Generate the `buzola.gen.ts` route module from a routes directory.
|
|
7
|
+
*
|
|
8
|
+
* Works standalone (no Vite required) — uses native `import()` as the
|
|
9
|
+
* default module loader. Pass a custom `moduleLoader` to integrate with
|
|
10
|
+
* Vite's `ssrLoadModule` or other bundlers.
|
|
11
|
+
*
|
|
12
|
+
* Note: The default module loader uses native `import()`, which can load
|
|
13
|
+
* `.ts`/`.tsx` files with Bun but not with Node.js. For Node.js, provide
|
|
14
|
+
* a custom `moduleLoader` (e.g. via Vite's `ssrLoadModule`).
|
|
15
|
+
*
|
|
16
|
+
* @returns `true` if the output file was written (content changed), `false` otherwise.
|
|
17
|
+
*/
|
|
18
|
+
export async function generate(options) {
|
|
19
|
+
const { routesDir, outputPath, persistentParams, moduleLoader = defaultModuleLoader } = options;
|
|
20
|
+
if (!fs.existsSync(routesDir)) {
|
|
21
|
+
throw new Error(`Routes directory does not exist: ${routesDir}`);
|
|
22
|
+
}
|
|
23
|
+
const files = scanRouteFiles(routesDir);
|
|
24
|
+
const tree = await buildFileRouteTree(files, moduleLoader);
|
|
25
|
+
const code = generateRouteModule({ tree, routesDir, outputPath, persistentParams });
|
|
26
|
+
return writeIfChanged(outputPath, code);
|
|
27
|
+
}
|
|
28
|
+
async function defaultModuleLoader(absolutePath) {
|
|
29
|
+
try {
|
|
30
|
+
return await import(absolutePath);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new Error(`Failed to load module "${absolutePath}". The default module loader uses native import() which `
|
|
34
|
+
+ `supports .ts/.tsx files with Bun but not Node.js. For Node.js, provide a custom moduleLoader option.`, { cause: error });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function writeIfChanged(filePath, content) {
|
|
38
|
+
if (fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf-8') === content) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAahE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAwB;IACtD,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,YAAY,GAAG,mBAAmB,EAAE,GAAG,OAAO,CAAA;IAE/F,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,oCAAoC,SAAS,EAAE,CAAC,CAAA;IACjE,CAAC;IAED,MAAM,KAAK,GAAG,cAAc,CAAC,SAAS,CAAC,CAAA;IACvC,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,KAAK,EAAE,YAAY,CAAC,CAAA;IAC1D,MAAM,IAAI,GAAG,mBAAmB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC,CAAA;IAEnF,OAAO,cAAc,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;AACxC,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,YAAoB;IACtD,IAAI,CAAC;QACJ,OAAO,MAAM,MAAM,CAAC,YAAY,CAAC,CAAA;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACd,0BAA0B,YAAY,0DAA0D;cAC7F,sGAAsG,EACzG,EAAE,KAAK,EAAE,KAAK,EAAE,CAChB,CAAA;IACF,CAAC;AACF,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB,EAAE,OAAe;IACxD,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,OAAO,EAAE,CAAC;QAC/E,OAAO,KAAK,CAAA;IACb,CAAC;IAED,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IAC5C,OAAO,IAAI,CAAA;AACZ,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { buzolaPlugin } from './plugin.js';
|
|
2
2
|
export type { BuzolaPluginOptions } from './plugin.js';
|
|
3
|
+
export { generate } from './generate.js';
|
|
4
|
+
export type { GenerateOptions } from './generate.js';
|
|
5
|
+
export { loadConfig } from './config.js';
|
|
6
|
+
export type { BuzolaConfig } from './config.js';
|
|
3
7
|
export { scanRouteFiles } from './generator/scanner.js';
|
|
4
8
|
export type { ScannedFile } from './generator/scanner.js';
|
|
5
9
|
export { buildFileRouteTree } from './generator/tree-builder.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEtD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,YAAY,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAEzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAChE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAEhF,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAE5D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAC5D,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAEhG,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAC3F,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEtD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEpD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,YAAY,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAEzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAChE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAEhF,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAE5D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAC5D,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAEhG,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAC3F,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { buzolaPlugin } from './plugin.js';
|
|
2
|
+
export { generate } from './generate.js';
|
|
3
|
+
export { loadConfig } from './config.js';
|
|
2
4
|
export { scanRouteFiles } from './generator/scanner.js';
|
|
3
5
|
export { buildFileRouteTree } from './generator/tree-builder.js';
|
|
4
6
|
export { generateRouteModule } from './generator/codegen.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG1C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAGvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAGhE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAG5D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAG5D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG1C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAGxC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAGvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAGhE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAG5D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAG5D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA"}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -30,6 +30,9 @@ export interface BuzolaPluginOptions {
|
|
|
30
30
|
* - BuzolaPageMap type augmentation
|
|
31
31
|
*
|
|
32
32
|
* Provides `virtual:buzola/routes` virtual module.
|
|
33
|
+
*
|
|
34
|
+
* Options can also be provided via `buzola.config.ts` in the project root.
|
|
35
|
+
* Plugin options take precedence over the config file.
|
|
33
36
|
*/
|
|
34
37
|
export declare function buzolaPlugin(options?: BuzolaPluginOptions): Plugin;
|
|
35
38
|
//# sourceMappingURL=plugin.d.ts.map
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAA;AAIjD,MAAM,WAAW,mBAAmB;IACnC;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gGAAgG;IAChG,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAqFtE"}
|
package/dist/plugin.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { buildFileRouteTree } from './generator/tree-builder.js';
|
|
3
|
+
import { loadConfig } from './config.js';
|
|
4
|
+
import { generate } from './generate.js';
|
|
6
5
|
/**
|
|
7
6
|
* Vite plugin for Buzola page-centric routing.
|
|
8
7
|
*
|
|
@@ -13,43 +12,41 @@ import { buildFileRouteTree } from './generator/tree-builder.js';
|
|
|
13
12
|
* - BuzolaPageMap type augmentation
|
|
14
13
|
*
|
|
15
14
|
* Provides `virtual:buzola/routes` virtual module.
|
|
15
|
+
*
|
|
16
|
+
* Options can also be provided via `buzola.config.ts` in the project root.
|
|
17
|
+
* Plugin options take precedence over the config file.
|
|
16
18
|
*/
|
|
17
19
|
export function buzolaPlugin(options = {}) {
|
|
18
|
-
const { name: nameOption
|
|
20
|
+
const { name: nameOption } = options;
|
|
19
21
|
const pluginName = nameOption ? `buzola:${nameOption}` : 'buzola';
|
|
20
22
|
const virtualModuleId = nameOption ? `virtual:buzola/${nameOption}/routes` : 'virtual:buzola/routes';
|
|
21
23
|
const resolvedVirtualId = '\0' + virtualModuleId;
|
|
22
24
|
let root;
|
|
23
|
-
let
|
|
24
|
-
let outputPath;
|
|
25
|
+
let baseOptions;
|
|
25
26
|
let server;
|
|
26
|
-
async function generate(moduleLoader) {
|
|
27
|
-
const files = scanRouteFiles(routesDir);
|
|
28
|
-
const tree = await buildFileRouteTree(files, moduleLoader);
|
|
29
|
-
const code = generateRouteModule({
|
|
30
|
-
tree,
|
|
31
|
-
routesDir,
|
|
32
|
-
outputPath,
|
|
33
|
-
persistentParams: persistentParamsOption,
|
|
34
|
-
});
|
|
35
|
-
return writeIfChanged(outputPath, code);
|
|
36
|
-
}
|
|
37
27
|
return {
|
|
38
28
|
name: pluginName,
|
|
39
29
|
enforce: 'pre',
|
|
40
|
-
configResolved(config) {
|
|
30
|
+
async configResolved(config) {
|
|
41
31
|
root = config.root;
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
const fileConfig = await loadConfig(root);
|
|
33
|
+
const routesDir = options.routesDir ?? fileConfig.routesDir ?? 'src/routes';
|
|
34
|
+
const output = options.output ?? fileConfig.output ?? 'src/buzola.gen.ts';
|
|
35
|
+
const persistentParams = options.persistentParams ?? fileConfig.persistentParams;
|
|
36
|
+
baseOptions = {
|
|
37
|
+
routesDir: path.resolve(root, routesDir),
|
|
38
|
+
outputPath: path.resolve(root, output),
|
|
39
|
+
persistentParams,
|
|
40
|
+
};
|
|
44
41
|
},
|
|
45
42
|
configureServer(srv) {
|
|
46
43
|
server = srv;
|
|
47
44
|
},
|
|
48
45
|
async buildStart() {
|
|
49
|
-
if (!fs.existsSync(routesDir))
|
|
46
|
+
if (!fs.existsSync(baseOptions.routesDir))
|
|
50
47
|
return;
|
|
51
48
|
if (server) {
|
|
52
|
-
await generate((p) => server.ssrLoadModule(p));
|
|
49
|
+
await generate({ ...baseOptions, moduleLoader: (p) => server.ssrLoadModule(p) });
|
|
53
50
|
}
|
|
54
51
|
else {
|
|
55
52
|
// Production build — create a temporary Vite server for module loading
|
|
@@ -61,7 +58,7 @@ export function buzolaPlugin(options = {}) {
|
|
|
61
58
|
optimizeDeps: { noDiscovery: true },
|
|
62
59
|
});
|
|
63
60
|
try {
|
|
64
|
-
await generate((p) => tempServer.ssrLoadModule(p));
|
|
61
|
+
await generate({ ...baseOptions, moduleLoader: (p) => tempServer.ssrLoadModule(p) });
|
|
65
62
|
}
|
|
66
63
|
finally {
|
|
67
64
|
await tempServer.close();
|
|
@@ -75,13 +72,13 @@ export function buzolaPlugin(options = {}) {
|
|
|
75
72
|
},
|
|
76
73
|
load(id) {
|
|
77
74
|
if (id === resolvedVirtualId) {
|
|
78
|
-
return `export { routes, pageRegistry } from '${outputPath.replace(/\.ts$/, '')}';\n`;
|
|
75
|
+
return `export { routes, pageRegistry } from '${baseOptions.outputPath.replace(/\.ts$/, '')}';\n`;
|
|
79
76
|
}
|
|
80
77
|
},
|
|
81
78
|
async handleHotUpdate({ file }) {
|
|
82
|
-
if (!file.startsWith(routesDir) || !server)
|
|
79
|
+
if (!file.startsWith(baseOptions.routesDir) || !server)
|
|
83
80
|
return;
|
|
84
|
-
const changed = await generate((p) => server.ssrLoadModule(p));
|
|
81
|
+
const changed = await generate({ ...baseOptions, moduleLoader: (p) => server.ssrLoadModule(p) });
|
|
85
82
|
if (!changed)
|
|
86
83
|
return;
|
|
87
84
|
// Invalidate the virtual module so Vite re-evaluates it.
|
|
@@ -96,11 +93,4 @@ export function buzolaPlugin(options = {}) {
|
|
|
96
93
|
},
|
|
97
94
|
};
|
|
98
95
|
}
|
|
99
|
-
function writeIfChanged(filePath, content) {
|
|
100
|
-
if (fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf-8') === content) {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
fs.writeFileSync(filePath, content, 'utf-8');
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
96
|
//# sourceMappingURL=plugin.js.map
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buzola/vite-plugin",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"buzola": "./dist/cli.js"
|
|
9
|
+
},
|
|
7
10
|
"exports": {
|
|
8
11
|
".": {
|
|
9
12
|
"bun": "./src/index.ts",
|
|
@@ -21,7 +24,7 @@
|
|
|
21
24
|
"directory": "packages/vite-plugin"
|
|
22
25
|
},
|
|
23
26
|
"dependencies": {
|
|
24
|
-
"@buzola/router": "^0.0.
|
|
27
|
+
"@buzola/router": "^0.0.10"
|
|
25
28
|
},
|
|
26
29
|
"devDependencies": {
|
|
27
30
|
"@types/node": "^25.3.3",
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as path from 'node:path'
|
|
3
|
+
import { parseArgs } from 'node:util'
|
|
4
|
+
import { loadConfig } from './config.js'
|
|
5
|
+
import { generate } from './generate.js'
|
|
6
|
+
|
|
7
|
+
const { values } = parseArgs({
|
|
8
|
+
options: {
|
|
9
|
+
'routes-dir': { type: 'string', short: 'r' },
|
|
10
|
+
output: { type: 'string', short: 'o' },
|
|
11
|
+
'persistent-param': { type: 'string', multiple: true },
|
|
12
|
+
root: { type: 'string' },
|
|
13
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
14
|
+
},
|
|
15
|
+
strict: true,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
if (values.help) {
|
|
19
|
+
console.log(`Usage: buzola-generate [options]
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
-r, --routes-dir <path> Routes directory (default: src/routes)
|
|
23
|
+
-o, --output <path> Output file path (default: src/buzola.gen.ts)
|
|
24
|
+
--persistent-param <name> Persistent parameter name (repeatable)
|
|
25
|
+
--root <path> Project root directory (default: cwd)
|
|
26
|
+
-h, --help Show this help message`)
|
|
27
|
+
process.exit(0)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const root = path.resolve(values.root ?? process.cwd())
|
|
31
|
+
const fileConfig = await loadConfig(root)
|
|
32
|
+
|
|
33
|
+
const routesDir = path.resolve(root, values['routes-dir'] ?? fileConfig.routesDir ?? 'src/routes')
|
|
34
|
+
const outputPath = path.resolve(root, values.output ?? fileConfig.output ?? 'src/buzola.gen.ts')
|
|
35
|
+
const persistentParams = values['persistent-param'] ?? fileConfig.persistentParams
|
|
36
|
+
|
|
37
|
+
const changed = await generate({ routesDir, outputPath, persistentParams })
|
|
38
|
+
|
|
39
|
+
if (changed) {
|
|
40
|
+
console.log(`Generated ${path.relative(root, outputPath)}`)
|
|
41
|
+
} else {
|
|
42
|
+
console.log('No changes')
|
|
43
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as path from 'node:path'
|
|
3
|
+
|
|
4
|
+
export interface BuzolaConfig {
|
|
5
|
+
/** Path to routes directory, relative to project root. Defaults to "src/routes". */
|
|
6
|
+
routesDir?: string
|
|
7
|
+
/** Path to output generated file, relative to project root. Defaults to "src/buzola.gen.ts". */
|
|
8
|
+
output?: string
|
|
9
|
+
/**
|
|
10
|
+
* Parameter names that should be persistent across navigations.
|
|
11
|
+
* When set, generates a BuzolaPersistentParams type augmentation
|
|
12
|
+
* so these params become optional in Link and navigate() calls.
|
|
13
|
+
*/
|
|
14
|
+
persistentParams?: string[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const CONFIG_FILES = ['buzola.config.ts', 'buzola.config.js', 'buzola.config.mjs']
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Load buzola config from the project root.
|
|
21
|
+
* Looks for `buzola.config.ts`, `buzola.config.js`, or `buzola.config.mjs`.
|
|
22
|
+
*
|
|
23
|
+
* @returns The loaded config, or an empty object if no config file is found.
|
|
24
|
+
*/
|
|
25
|
+
export async function loadConfig(root: string): Promise<BuzolaConfig> {
|
|
26
|
+
for (const file of CONFIG_FILES) {
|
|
27
|
+
const configPath = path.resolve(root, file)
|
|
28
|
+
if (!fs.existsSync(configPath)) continue
|
|
29
|
+
|
|
30
|
+
const mod = await import(configPath)
|
|
31
|
+
return mod.default ?? mod
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {}
|
|
35
|
+
}
|
package/src/generate.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import { generateRouteModule } from './generator/codegen.js'
|
|
3
|
+
import type { ModuleLoader } from './generator/page-extractor.js'
|
|
4
|
+
import { scanRouteFiles } from './generator/scanner.js'
|
|
5
|
+
import { buildFileRouteTree } from './generator/tree-builder.js'
|
|
6
|
+
|
|
7
|
+
export interface GenerateOptions {
|
|
8
|
+
/** Absolute path to the routes directory. */
|
|
9
|
+
routesDir: string
|
|
10
|
+
/** Absolute path to the output file. */
|
|
11
|
+
outputPath: string
|
|
12
|
+
/** Parameter names that should be persistent across navigations. */
|
|
13
|
+
persistentParams?: string[]
|
|
14
|
+
/** Custom module loader. Defaults to native `import()`. */
|
|
15
|
+
moduleLoader?: ModuleLoader
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generate the `buzola.gen.ts` route module from a routes directory.
|
|
20
|
+
*
|
|
21
|
+
* Works standalone (no Vite required) — uses native `import()` as the
|
|
22
|
+
* default module loader. Pass a custom `moduleLoader` to integrate with
|
|
23
|
+
* Vite's `ssrLoadModule` or other bundlers.
|
|
24
|
+
*
|
|
25
|
+
* Note: The default module loader uses native `import()`, which can load
|
|
26
|
+
* `.ts`/`.tsx` files with Bun but not with Node.js. For Node.js, provide
|
|
27
|
+
* a custom `moduleLoader` (e.g. via Vite's `ssrLoadModule`).
|
|
28
|
+
*
|
|
29
|
+
* @returns `true` if the output file was written (content changed), `false` otherwise.
|
|
30
|
+
*/
|
|
31
|
+
export async function generate(options: GenerateOptions): Promise<boolean> {
|
|
32
|
+
const { routesDir, outputPath, persistentParams, moduleLoader = defaultModuleLoader } = options
|
|
33
|
+
|
|
34
|
+
if (!fs.existsSync(routesDir)) {
|
|
35
|
+
throw new Error(`Routes directory does not exist: ${routesDir}`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const files = scanRouteFiles(routesDir)
|
|
39
|
+
const tree = await buildFileRouteTree(files, moduleLoader)
|
|
40
|
+
const code = generateRouteModule({ tree, routesDir, outputPath, persistentParams })
|
|
41
|
+
|
|
42
|
+
return writeIfChanged(outputPath, code)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function defaultModuleLoader(absolutePath: string): Promise<Record<string, unknown>> {
|
|
46
|
+
try {
|
|
47
|
+
return await import(absolutePath)
|
|
48
|
+
} catch (error) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Failed to load module "${absolutePath}". The default module loader uses native import() which `
|
|
51
|
+
+ `supports .ts/.tsx files with Bun but not Node.js. For Node.js, provide a custom moduleLoader option.`,
|
|
52
|
+
{ cause: error },
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function writeIfChanged(filePath: string, content: string): boolean {
|
|
58
|
+
if (fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf-8') === content) {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(filePath, content, 'utf-8')
|
|
63
|
+
return true
|
|
64
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export { buzolaPlugin } from './plugin.js'
|
|
2
2
|
export type { BuzolaPluginOptions } from './plugin.js'
|
|
3
3
|
|
|
4
|
+
export { generate } from './generate.js'
|
|
5
|
+
export type { GenerateOptions } from './generate.js'
|
|
6
|
+
|
|
7
|
+
export { loadConfig } from './config.js'
|
|
8
|
+
export type { BuzolaConfig } from './config.js'
|
|
9
|
+
|
|
4
10
|
export { scanRouteFiles } from './generator/scanner.js'
|
|
5
11
|
export type { ScannedFile } from './generator/scanner.js'
|
|
6
12
|
|
package/src/plugin.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import * as fs from 'node:fs'
|
|
2
2
|
import * as path from 'node:path'
|
|
3
3
|
import type { Plugin, ViteDevServer } from 'vite'
|
|
4
|
-
import {
|
|
5
|
-
import type
|
|
6
|
-
import { scanRouteFiles } from './generator/scanner.js'
|
|
7
|
-
import { buildFileRouteTree } from './generator/tree-builder.js'
|
|
4
|
+
import { loadConfig } from './config.js'
|
|
5
|
+
import { generate, type GenerateOptions } from './generate.js'
|
|
8
6
|
|
|
9
7
|
export interface BuzolaPluginOptions {
|
|
10
8
|
/**
|
|
@@ -38,45 +36,38 @@ export interface BuzolaPluginOptions {
|
|
|
38
36
|
* - BuzolaPageMap type augmentation
|
|
39
37
|
*
|
|
40
38
|
* Provides `virtual:buzola/routes` virtual module.
|
|
39
|
+
*
|
|
40
|
+
* Options can also be provided via `buzola.config.ts` in the project root.
|
|
41
|
+
* Plugin options take precedence over the config file.
|
|
41
42
|
*/
|
|
42
43
|
export function buzolaPlugin(options: BuzolaPluginOptions = {}): Plugin {
|
|
43
|
-
const {
|
|
44
|
-
name: nameOption,
|
|
45
|
-
routesDir: routesDirOption = 'src/routes',
|
|
46
|
-
output: outputOption = 'src/buzola.gen.ts',
|
|
47
|
-
persistentParams: persistentParamsOption,
|
|
48
|
-
} = options
|
|
44
|
+
const { name: nameOption } = options
|
|
49
45
|
|
|
50
46
|
const pluginName = nameOption ? `buzola:${nameOption}` : 'buzola'
|
|
51
47
|
const virtualModuleId = nameOption ? `virtual:buzola/${nameOption}/routes` : 'virtual:buzola/routes'
|
|
52
48
|
const resolvedVirtualId = '\0' + virtualModuleId
|
|
53
49
|
|
|
54
50
|
let root: string
|
|
55
|
-
let
|
|
56
|
-
let outputPath: string
|
|
51
|
+
let baseOptions: Omit<GenerateOptions, 'moduleLoader'>
|
|
57
52
|
let server: ViteDevServer | undefined
|
|
58
53
|
|
|
59
|
-
async function generate(moduleLoader: ModuleLoader): Promise<boolean> {
|
|
60
|
-
const files = scanRouteFiles(routesDir)
|
|
61
|
-
const tree = await buildFileRouteTree(files, moduleLoader)
|
|
62
|
-
const code = generateRouteModule({
|
|
63
|
-
tree,
|
|
64
|
-
routesDir,
|
|
65
|
-
outputPath,
|
|
66
|
-
persistentParams: persistentParamsOption,
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
return writeIfChanged(outputPath, code)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
54
|
return {
|
|
73
55
|
name: pluginName,
|
|
74
56
|
enforce: 'pre',
|
|
75
57
|
|
|
76
|
-
configResolved(config) {
|
|
58
|
+
async configResolved(config) {
|
|
77
59
|
root = config.root
|
|
78
|
-
|
|
79
|
-
|
|
60
|
+
const fileConfig = await loadConfig(root)
|
|
61
|
+
|
|
62
|
+
const routesDir = options.routesDir ?? fileConfig.routesDir ?? 'src/routes'
|
|
63
|
+
const output = options.output ?? fileConfig.output ?? 'src/buzola.gen.ts'
|
|
64
|
+
const persistentParams = options.persistentParams ?? fileConfig.persistentParams
|
|
65
|
+
|
|
66
|
+
baseOptions = {
|
|
67
|
+
routesDir: path.resolve(root, routesDir),
|
|
68
|
+
outputPath: path.resolve(root, output),
|
|
69
|
+
persistentParams,
|
|
70
|
+
}
|
|
80
71
|
},
|
|
81
72
|
|
|
82
73
|
configureServer(srv) {
|
|
@@ -84,10 +75,10 @@ export function buzolaPlugin(options: BuzolaPluginOptions = {}): Plugin {
|
|
|
84
75
|
},
|
|
85
76
|
|
|
86
77
|
async buildStart() {
|
|
87
|
-
if (!fs.existsSync(routesDir)) return
|
|
78
|
+
if (!fs.existsSync(baseOptions.routesDir)) return
|
|
88
79
|
|
|
89
80
|
if (server) {
|
|
90
|
-
await generate((p) => server!.ssrLoadModule(p))
|
|
81
|
+
await generate({ ...baseOptions, moduleLoader: (p) => server!.ssrLoadModule(p) })
|
|
91
82
|
} else {
|
|
92
83
|
// Production build — create a temporary Vite server for module loading
|
|
93
84
|
const { createServer } = await import('vite')
|
|
@@ -98,7 +89,7 @@ export function buzolaPlugin(options: BuzolaPluginOptions = {}): Plugin {
|
|
|
98
89
|
optimizeDeps: { noDiscovery: true },
|
|
99
90
|
})
|
|
100
91
|
try {
|
|
101
|
-
await generate((p) => tempServer.ssrLoadModule(p))
|
|
92
|
+
await generate({ ...baseOptions, moduleLoader: (p) => tempServer.ssrLoadModule(p) })
|
|
102
93
|
} finally {
|
|
103
94
|
await tempServer.close()
|
|
104
95
|
}
|
|
@@ -113,14 +104,14 @@ export function buzolaPlugin(options: BuzolaPluginOptions = {}): Plugin {
|
|
|
113
104
|
|
|
114
105
|
load(id) {
|
|
115
106
|
if (id === resolvedVirtualId) {
|
|
116
|
-
return `export { routes, pageRegistry } from '${outputPath.replace(/\.ts$/, '')}';\n`
|
|
107
|
+
return `export { routes, pageRegistry } from '${baseOptions.outputPath.replace(/\.ts$/, '')}';\n`
|
|
117
108
|
}
|
|
118
109
|
},
|
|
119
110
|
|
|
120
111
|
async handleHotUpdate({ file }) {
|
|
121
|
-
if (!file.startsWith(routesDir) || !server) return
|
|
112
|
+
if (!file.startsWith(baseOptions.routesDir) || !server) return
|
|
122
113
|
|
|
123
|
-
const changed = await generate((p) => server!.ssrLoadModule(p))
|
|
114
|
+
const changed = await generate({ ...baseOptions, moduleLoader: (p) => server!.ssrLoadModule(p) })
|
|
124
115
|
if (!changed) return
|
|
125
116
|
|
|
126
117
|
// Invalidate the virtual module so Vite re-evaluates it.
|
|
@@ -135,12 +126,3 @@ export function buzolaPlugin(options: BuzolaPluginOptions = {}): Plugin {
|
|
|
135
126
|
},
|
|
136
127
|
}
|
|
137
128
|
}
|
|
138
|
-
|
|
139
|
-
function writeIfChanged(filePath: string, content: string): boolean {
|
|
140
|
-
if (fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf-8') === content) {
|
|
141
|
-
return false
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
fs.writeFileSync(filePath, content, 'utf-8')
|
|
145
|
-
return true
|
|
146
|
-
}
|