@ubean/vite 0.1.2 → 0.1.3

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.
@@ -0,0 +1,18 @@
1
+ import { ScannedAppEntry, ScannedLayout, ScannedPageRoute, ScannedServerEntry } from "@ubean/routing";
2
+ import { Plugin } from "vite";
3
+ import { ResolvedConfig } from "@ubean/config";
4
+ //#region src/plugin.d.ts
5
+ interface UbeanVuePluginOptions {
6
+ config: ResolvedConfig;
7
+ ssr?: boolean;
8
+ }
9
+ declare const VUE_PLUGIN_INCLUDE: RegExp[];
10
+ declare function ubeanVuePlugin(_options: UbeanVuePluginOptions): Plugin[];
11
+ //#endregion
12
+ //#region src/virtual-modules.d.ts
13
+ declare function createVuePagesVirtualModule(pages: ScannedPageRoute[], layouts: ScannedLayout[]): any;
14
+ declare function createVueAppEntryVirtualModule(appEntry?: ScannedAppEntry): any;
15
+ declare function createClientEntryVirtualModule(): any;
16
+ declare function createServerEntryVirtualModule(serverEntry?: ScannedServerEntry): any;
17
+ //#endregion
18
+ export { type UbeanVuePluginOptions, VUE_PLUGIN_INCLUDE, createClientEntryVirtualModule, createServerEntryVirtualModule, createVueAppEntryVirtualModule, createVuePagesVirtualModule, ubeanVuePlugin };
package/dist/index.js ADDED
@@ -0,0 +1,584 @@
1
+ import Components from "unplugin-vue-components/vite";
2
+ import Markdown from "unplugin-vue-markdown/vite";
3
+ import AutoImport from "unplugin-auto-import/vite";
4
+ import { UBEAN_CLIENT_PRESET, UBEAN_SERVER_PRESET } from "@ubean/auto-imports";
5
+ import { defineVirtualModule, getComponentResolvers, getCssImports, useVirtualRegistry } from "@ubean/build";
6
+ import { scanProject } from "@ubean/routing";
7
+ import { transformSync } from "oxc-transform";
8
+ import { join } from "pathe";
9
+ //#region src/virtual-modules.ts
10
+ /**
11
+ * Convert ubean route path to vue-router compatible path.
12
+ * - `**:param` (catch-all) → `:param(.*)*`
13
+ * - `:param?` (optional) → `:param?` (already compatible)
14
+ * - `:param` (dynamic) → `:param` (already compatible)
15
+ */
16
+ function toVueRouterPath(route) {
17
+ return route.replace(/\*\*:(\w[\w-]*)/g, ":$1(.*)*");
18
+ }
19
+ /**
20
+ * Sort pages so that reuse routes come after their targets.
21
+ *
22
+ * A reuse route generates `const Page_Reuse = Page_Target;` in the virtual
23
+ * module. If the reuse route is declared before its target, JavaScript's TDZ
24
+ * (temporal dead zone) throws `Cannot access 'Page_Target' before initialization`.
25
+ *
26
+ * This topological sort ensures targets are always emitted before their
27
+ * reuse routes. Pages without reuse dependencies retain their original order.
28
+ */
29
+ function sortPagesByReuseDependency(pages) {
30
+ const result = [];
31
+ const visited = /* @__PURE__ */ new Set();
32
+ const byName = /* @__PURE__ */ new Map();
33
+ for (const p of pages) byName.set(p.name, p);
34
+ function visit(page) {
35
+ if (visited.has(page.name)) return;
36
+ visited.add(page.name);
37
+ if (page.isReuse && page.reuseTarget && byName.has(page.reuseTarget)) visit(byName.get(page.reuseTarget));
38
+ result.push(page);
39
+ }
40
+ for (const p of pages) visit(p);
41
+ return result;
42
+ }
43
+ function createVuePagesVirtualModule(pages, layouts) {
44
+ return defineVirtualModule("virtual:ubean-pages", () => {
45
+ const pageLoaders = [];
46
+ const layoutLoaders = [];
47
+ const routeEntries = [];
48
+ const pageByName = /* @__PURE__ */ new Map();
49
+ for (const p of pages) pageByName.set(p.name, p);
50
+ const sortedPages = sortPagesByReuseDependency(pages);
51
+ for (const p of sortedPages) {
52
+ const varName = `Page_${p.name.replace(/[^a-zA-Z0-9]/g, "_")}`;
53
+ const routerPath = toVueRouterPath(p.route);
54
+ const layoutValue = p.layout === false ? "false" : p.layout ? JSON.stringify(p.layout) : "undefined";
55
+ const cacheValue = p.cache === true ? "true" : "undefined";
56
+ if (p.isReuse && p.reuseTarget && pageByName.has(p.reuseTarget)) {
57
+ const targetVarName = `Page_${p.reuseTarget.replace(/[^a-zA-Z0-9]/g, "_")}`;
58
+ pageLoaders.push(`const ${varName} = ${targetVarName};`);
59
+ } else pageLoaders.push(`const ${varName} = () => import(${JSON.stringify(p.fullPath)}).then(m => m.default || m);`);
60
+ routeEntries.push(` { path: ${JSON.stringify(routerPath)}, name: ${JSON.stringify(p.name)}, component: ${varName}, meta: { layout: ${layoutValue}, pageName: ${JSON.stringify(p.name)}, cache: ${cacheValue} } }`);
61
+ }
62
+ for (const l of layouts) {
63
+ const varName = `Layout_${l.name.replace(/[^a-zA-Z0-9]/g, "_")}`;
64
+ layoutLoaders.push(`const ${varName} = () => import(${JSON.stringify(l.fullPath)}).then(m => m.default || m);`);
65
+ }
66
+ const defaultLayout = layouts.find((l) => l.isDefault);
67
+ const defaultLayoutName = defaultLayout ? defaultLayout.name : "null";
68
+ const layoutLoaderMap = layouts.map((l) => {
69
+ const varName = `Layout_${l.name.replace(/[^a-zA-Z0-9]/g, "_")}`;
70
+ return ` ${JSON.stringify(l.name)}: ${varName}`;
71
+ }).join(",\n");
72
+ return `${`
73
+ // Auto-generated by ubean - do not edit
74
+ /* eslint-disable */
75
+
76
+ ${pageLoaders.join("\n")}
77
+ ${layoutLoaders.join("\n")}
78
+
79
+ export const routes = [
80
+ ${routeEntries.join(",\n")}
81
+ ];
82
+
83
+ const _layoutLoaders = {
84
+ ${layoutLoaderMap}
85
+ };
86
+
87
+ const _pageLoaders = {
88
+ ${sortedPages.map((p) => ` ${JSON.stringify(p.name)}: ${`Page_${p.name.replace(/[^a-zA-Z0-9]/g, "_")}`}`).join(",\n")}
89
+ };
90
+
91
+ export const pageNames = [${sortedPages.map((p) => JSON.stringify(p.name)).join(", ")}] as const;
92
+ export const layoutNames = [${layouts.map((l) => JSON.stringify(l.name)).join(", ")}] as const;
93
+ export const defaultLayout = ${defaultLayoutName === "null" ? "null" : JSON.stringify(defaultLayoutName)} as const;
94
+
95
+ export type RouteName = (typeof pageNames)[number];
96
+ export type LayoutName = (typeof layoutNames)[number];
97
+
98
+ export function resolvePageComponent(name) {
99
+ const loader = _pageLoaders[name];
100
+ if (!loader) {
101
+ return Promise.reject(new Error('[ubean] Page component not found: ' + name));
102
+ }
103
+ return loader();
104
+ }
105
+
106
+ export function resolveLayoutComponent(name) {
107
+ if (!name) return Promise.resolve(null);
108
+ const loader = _layoutLoaders[name];
109
+ if (!loader) {
110
+ return Promise.resolve(null);
111
+ }
112
+ return loader();
113
+ }
114
+
115
+ export const pages = {
116
+ ${pages.map((p) => ` ${JSON.stringify(p.name)}: { name: ${JSON.stringify(p.name)}, route: ${JSON.stringify(p.route)}, path: ${JSON.stringify(p.path)}, layout: ${JSON.stringify(p.layout)}, isReuse: ${p.isReuse}, reuseTarget: ${JSON.stringify(p.reuseTarget)} }`).join(",\n")}
117
+ };
118
+
119
+ export const layouts = {
120
+ ${layouts.map((l) => ` ${JSON.stringify(l.name)}: { name: ${JSON.stringify(l.name)}, isDefault: ${l.isDefault} }`).join(",\n")}
121
+ };
122
+
123
+ export default {
124
+ routes,
125
+ pageNames,
126
+ layoutNames,
127
+ defaultLayout,
128
+ resolvePageComponent,
129
+ resolveLayoutComponent,
130
+ pages,
131
+ layouts
132
+ };
133
+ `.trim()}\n`;
134
+ });
135
+ }
136
+ const EMPTY_APP_ENTRY = {
137
+ shared: { exists: false },
138
+ server: { exists: false },
139
+ client: { exists: false }
140
+ };
141
+ function createVueAppEntryVirtualModule(appEntry = EMPTY_APP_ENTRY) {
142
+ return defineVirtualModule("virtual:ubean-app", () => {
143
+ const hasSharedApp = appEntry.shared.exists;
144
+ const hasServerApp = appEntry.server.exists;
145
+ const hasClientApp = appEntry.client.exists;
146
+ return `${`
147
+ // Auto-generated by ubean - do not edit
148
+ /* eslint-disable */
149
+ ${hasSharedApp ? `import _sharedApp from ${JSON.stringify(appEntry.shared.fullPath)};` : "const _sharedApp = null;"}
150
+ ${hasServerApp ? `import _serverApp from ${JSON.stringify(appEntry.server.fullPath)};` : "const _serverApp = null;"}
151
+ ${hasClientApp ? `import _clientApp from ${JSON.stringify(appEntry.client.fullPath)};` : "const _clientApp = null;"}
152
+
153
+ import {
154
+ createUbeanApp,
155
+ createUbeanSSRApp,
156
+ usePage,
157
+ useRouter,
158
+ useHead,
159
+ useSeoMeta,
160
+ Link,
161
+ Head,
162
+ getInitialPageData,
163
+ defineApp,
164
+ applyAppConfig,
165
+ createDefaultAppConfig,
166
+ createClientHead,
167
+ createServerHead
168
+ } from 'ubean/runtime/vue';
169
+
170
+ export {
171
+ createUbeanApp,
172
+ createUbeanSSRApp,
173
+ usePage,
174
+ useRouter,
175
+ useHead,
176
+ useSeoMeta,
177
+ Link,
178
+ Head,
179
+ getInitialPageData,
180
+ defineApp,
181
+ applyAppConfig,
182
+ createDefaultAppConfig
183
+ };
184
+
185
+ import {
186
+ routes,
187
+ resolvePageComponent,
188
+ resolveLayoutComponent,
189
+ defaultLayout,
190
+ pageNames,
191
+ layoutNames
192
+ } from 'virtual:ubean-pages';
193
+
194
+ export {
195
+ routes,
196
+ resolvePageComponent,
197
+ resolveLayoutComponent,
198
+ defaultLayout,
199
+ pageNames,
200
+ layoutNames
201
+ };
202
+
203
+ function _mergeAppConfig(base, ...configs) {
204
+ const result = { ...base };
205
+ for (const cfg of configs) {
206
+ if (!cfg) continue;
207
+ if (cfg.plugins) result.plugins = [...(result.plugins || []), ...cfg.plugins];
208
+ if (cfg.globalComponents) result.globalComponents = { ...(result.globalComponents || {}), ...cfg.globalComponents };
209
+ if (cfg.provides) result.provides = { ...(result.provides || {}), ...cfg.provides };
210
+ if (cfg.head) result.head = { ...(result.head || {}), ...cfg.head };
211
+ if (cfg.rootId) result.rootId = cfg.rootId;
212
+ if (cfg.rootAttrs) result.rootAttrs = { ...(result.rootAttrs || {}), ...cfg.rootAttrs };
213
+ if (cfg.onAppCreated) result.onAppCreated = cfg.onAppCreated;
214
+ if (cfg.onClientReady) result.onClientReady = cfg.onClientReady;
215
+ if (cfg.errorComponent) result.errorComponent = cfg.errorComponent;
216
+ if (cfg.loadingComponent) result.loadingComponent = cfg.loadingComponent;
217
+ if (cfg.viewTransitions !== undefined) result.viewTransitions = cfg.viewTransitions;
218
+ }
219
+ // router.setup:累加语义 — shared 和 client/server 各自定义的 setup 都会执行
220
+ // (顺序:shared 先,client/server 后)。这样 shared 可放通用守卫(如埋点),
221
+ // client/server 可放环境专用守卫(如 SSR 鉴权)。
222
+ const setups = [];
223
+ for (const cfg of [base, ...configs]) {
224
+ if (cfg?.router?.setup) setups.push(cfg.router.setup);
225
+ }
226
+ if (setups.length === 1) {
227
+ result.router = { setup: setups[0] };
228
+ } else if (setups.length > 1) {
229
+ result.router = {
230
+ setup: (router) => {
231
+ for (const s of setups) s(router);
232
+ }
233
+ };
234
+ }
235
+ return result;
236
+ }
237
+
238
+ export function resolveAppConfig(mode) {
239
+ const base = createDefaultAppConfig();
240
+ if (!_sharedApp && !_serverApp && !_clientApp) return base;
241
+
242
+ const sharedCfg = typeof _sharedApp === 'function' ? _sharedApp() : _sharedApp;
243
+ const serverCfg = mode === 'server' && _serverApp ? (typeof _serverApp === 'function' ? _serverApp() : _serverApp) : null;
244
+ const clientCfg = mode === 'client' && _clientApp ? (typeof _clientApp === 'function' ? _clientApp() : _clientApp) : null;
245
+
246
+ const merged = _mergeAppConfig(base, sharedCfg, mode === 'server' ? serverCfg : clientCfg);
247
+
248
+ // onAppCreated: shared config is the default; server/client-specific overrides it if present.
249
+ if (sharedCfg?.onAppCreated) merged.onAppCreated = sharedCfg.onAppCreated;
250
+ if (mode === 'client' && clientCfg?.onClientReady) merged.onClientReady = clientCfg.onClientReady;
251
+ else if (mode === 'server' && serverCfg?.onAppCreated) merged.onAppCreated = serverCfg.onAppCreated;
252
+
253
+ return merged;
254
+ }
255
+
256
+ export function createApp() {
257
+ const config = resolveAppConfig('client');
258
+ const head = createClientHead();
259
+
260
+ // Push global app head (from defineApp) as defaults before any page-level head.
261
+ if (config.head) {
262
+ const headInput = {};
263
+ if (config.head.title) headInput.title = config.head.title;
264
+ if (config.head.htmlAttrs) headInput.htmlAttrs = config.head.htmlAttrs;
265
+ if (config.head.bodyAttrs) headInput.bodyAttrs = config.head.bodyAttrs;
266
+ if (config.head.meta) headInput.meta = config.head.meta;
267
+ if (config.head.link) headInput.link = config.head.link;
268
+ if (config.head.script) headInput.script = config.head.script;
269
+ head.push(headInput);
270
+ }
271
+
272
+ const initialPage = getInitialPageData();
273
+ const instance = createUbeanApp({
274
+ routes,
275
+ resolveLayoutComponent,
276
+ defaultLayout,
277
+ head,
278
+ viewTransitions: config.viewTransitions,
279
+ initialPage: initialPage || undefined,
280
+ hydrate: !!initialPage,
281
+ routerSetup: config.router?.setup
282
+ });
283
+
284
+ applyAppConfig(instance.app, config, 'client');
285
+
286
+ if (config.onAppCreated) config.onAppCreated(instance.app);
287
+
288
+ const mountApp = () => {
289
+ instance.app.mount('#' + (config.rootId || 'app'));
290
+ if (config.onClientReady) {
291
+ config.onClientReady(instance.app);
292
+ }
293
+ };
294
+
295
+ // When hydrating SSR content, must wait for router to be ready before mounting,
296
+ // otherwise RouterView has no matched route and causes hydration mismatch.
297
+ if (initialPage) {
298
+ instance.router.isReady().then(mountApp);
299
+ } else {
300
+ mountApp();
301
+ }
302
+
303
+ return instance;
304
+ }
305
+
306
+ export function createSSRApp(initialPage) {
307
+ const config = resolveAppConfig('server');
308
+ const head = createServerHead();
309
+
310
+ const { app, router } = createUbeanSSRApp(initialPage, {
311
+ routes,
312
+ resolveLayoutComponent,
313
+ defaultLayout,
314
+ head,
315
+ routerSetup: config.router?.setup
316
+ });
317
+
318
+ applyAppConfig(app, config, 'server');
319
+
320
+ if (config.onAppCreated) config.onAppCreated(app);
321
+
322
+ return { app, router, head, config };
323
+ }
324
+
325
+ export const rootId = 'app';
326
+ `.trim()}\n`;
327
+ });
328
+ }
329
+ function createClientEntryVirtualModule() {
330
+ return defineVirtualModule("virtual:ubean-client-entry", () => {
331
+ return `${`
332
+ // Auto-generated by ubean client entry - do not edit
333
+ /* eslint-disable */
334
+ ${getCssImports().map((css) => `import ${JSON.stringify(css)};`).join("\n")}
335
+ import { createApp } from 'virtual:ubean-app';
336
+
337
+ createApp();
338
+ `.trim()}\n`;
339
+ });
340
+ }
341
+ const EMPTY_SERVER_ENTRY = {
342
+ shared: { exists: false },
343
+ dev: { exists: false },
344
+ prod: { exists: false }
345
+ };
346
+ function createServerEntryVirtualModule(serverEntry = EMPTY_SERVER_ENTRY) {
347
+ return defineVirtualModule("virtual:ubean-server", () => {
348
+ const hasShared = serverEntry.shared.exists;
349
+ const hasDev = serverEntry.dev.exists;
350
+ const hasProd = serverEntry.prod.exists;
351
+ return `${`
352
+ // Auto-generated by ubean server entry - do not edit
353
+ /* eslint-disable */
354
+ ${hasShared ? `import _sharedServer from ${JSON.stringify(serverEntry.shared.fullPath)};` : "const _sharedServer = null;"}
355
+ ${hasDev ? `import _devServer from ${JSON.stringify(serverEntry.dev.fullPath)};` : "const _devServer = null;"}
356
+ ${hasProd ? `import _prodServer from ${JSON.stringify(serverEntry.prod.fullPath)};` : "const _prodServer = null;"}
357
+
358
+ import {
359
+ defineServer,
360
+ createDefaultServerConfig,
361
+ mergeServerConfigs
362
+ } from 'ubean/runtime/app';
363
+
364
+ export {
365
+ defineServer,
366
+ createDefaultServerConfig,
367
+ mergeServerConfigs
368
+ };
369
+
370
+ /**
371
+ * Resolve the user's server config for the given mode ('dev' | 'prod').
372
+ * Merges shared config with mode-specific config. Returns an empty
373
+ * config when no server entry file exists.
374
+ */
375
+ export function resolveServerConfig(mode) {
376
+ const base = createDefaultServerConfig();
377
+
378
+ if (!_sharedServer && !_devServer && !_prodServer) return base;
379
+
380
+ const sharedCfg = typeof _sharedServer === 'function' ? _sharedServer() : _sharedServer;
381
+ const modeCfg = mode === 'dev'
382
+ ? (typeof _devServer === 'function' ? _devServer() : _devServer)
383
+ : (typeof _prodServer === 'function' ? _prodServer() : _prodServer);
384
+
385
+ return mergeServerConfigs(base, sharedCfg, modeCfg);
386
+ }
387
+ `.trim()}\n`;
388
+ });
389
+ }
390
+ //#endregion
391
+ //#region src/plugin.ts
392
+ const VUE_PLUGIN_INCLUDE = [/\.vue$/, /\.md$/];
393
+ const VIRTUAL_PAGES = "virtual:ubean-pages";
394
+ const VIRTUAL_APP = "virtual:ubean-app";
395
+ const VIRTUAL_CLIENT = "virtual:ubean-client-entry";
396
+ const VIRTUAL_SERVER = "virtual:ubean-server";
397
+ const CLIENT_ENTRY_URL = `/@id/${VIRTUAL_CLIENT}`;
398
+ const TS_VIRTUAL_IDS = [
399
+ VIRTUAL_PAGES,
400
+ VIRTUAL_APP,
401
+ VIRTUAL_SERVER
402
+ ];
403
+ const VIRTUAL_IDS = [
404
+ VIRTUAL_PAGES,
405
+ VIRTUAL_APP,
406
+ VIRTUAL_CLIENT,
407
+ VIRTUAL_SERVER
408
+ ];
409
+ const HASH_ID_TO_VIRTUAL = {
410
+ "#ubean-pages": VIRTUAL_PAGES,
411
+ "#ubean-app": VIRTUAL_APP,
412
+ "#ubean-client-entry": VIRTUAL_CLIENT,
413
+ "#ubean-server": VIRTUAL_SERVER
414
+ };
415
+ const NULL_PREFIX = "\0";
416
+ const VIRTUAL_EXT = ".ts";
417
+ function toResolvedVirtualId(virtualId) {
418
+ return NULL_PREFIX + virtualId + VIRTUAL_EXT;
419
+ }
420
+ function parseResolvedVirtualId(resolvedId) {
421
+ if (!resolvedId.startsWith(NULL_PREFIX)) return void 0;
422
+ const withoutPrefix = resolvedId.slice(1);
423
+ if (!withoutPrefix.endsWith(VIRTUAL_EXT)) return void 0;
424
+ const virtualId = withoutPrefix.slice(0, -3);
425
+ return VIRTUAL_IDS.includes(virtualId) ? virtualId : void 0;
426
+ }
427
+ function ubeanVuePlugin(_options) {
428
+ const { config: ubeanConfig } = _options;
429
+ const virtualRegistry = useVirtualRegistry();
430
+ const srcDir = join(ubeanConfig.rootDir, ubeanConfig.srcDir);
431
+ const dtsDir = join(ubeanConfig.rootDir, ".ubean");
432
+ const markdownEnabled = ubeanConfig.markdown?.enabled !== false;
433
+ const mdxEnabled = ubeanConfig.markdown?.mdx === true;
434
+ const autoImportEnabled = ubeanConfig.imports.autoImport !== false;
435
+ const componentAutoImportEnabled = ubeanConfig.components.autoImport !== false;
436
+ const markdownComponentsAutoImport = ubeanConfig.markdown?.components?.autoImport !== false;
437
+ const directoryAsNamespace = ubeanConfig.components.directoryAsNamespace ?? false;
438
+ const composablesDirName = ubeanConfig.dir.composables || "composables";
439
+ const componentsDirName = ubeanConfig.dir.components || "components";
440
+ const composablesDirs = [join(srcDir, composablesDirName), ...ubeanConfig.imports.dirs || []];
441
+ const componentsDirs = [join(srcDir, componentsDirName), ...ubeanConfig.components.dirs || []];
442
+ const mdExtensions = mdxEnabled ? ["md", "mdx"] : ["md"];
443
+ function getVirtualModule(virtualId) {
444
+ return virtualRegistry.getModules().find((m) => m.id === virtualId);
445
+ }
446
+ async function loadVirtualModule(virtualId) {
447
+ const mod = getVirtualModule(virtualId);
448
+ if (!mod) return void 0;
449
+ return mod.load();
450
+ }
451
+ async function scanAndRegister() {
452
+ const result = await scanProject({
453
+ cwd: ubeanConfig.rootDir,
454
+ srcDir: ubeanConfig.srcDir,
455
+ dirs: ubeanConfig.dir,
456
+ ignore: ubeanConfig.scanOptions?.ignore
457
+ });
458
+ virtualRegistry.register(createVuePagesVirtualModule(result.pages, result.layouts));
459
+ virtualRegistry.register(createVueAppEntryVirtualModule(result.appEntry));
460
+ virtualRegistry.register(createServerEntryVirtualModule(result.serverEntry));
461
+ virtualRegistry.register(createClientEntryVirtualModule());
462
+ }
463
+ const HASH_IDS = Object.keys(HASH_ID_TO_VIRTUAL);
464
+ const plugins = [{
465
+ name: "ubean:vue",
466
+ enforce: "pre",
467
+ async buildStart() {
468
+ await scanAndRegister();
469
+ },
470
+ resolveId(id) {
471
+ if (HASH_ID_TO_VIRTUAL[id]) return toResolvedVirtualId(HASH_ID_TO_VIRTUAL[id]);
472
+ if (VIRTUAL_IDS.includes(id)) return toResolvedVirtualId(id);
473
+ },
474
+ async load(id) {
475
+ const virtualId = parseResolvedVirtualId(id);
476
+ if (virtualId) {
477
+ let code = await loadVirtualModule(virtualId);
478
+ if (code && TS_VIRTUAL_IDS.includes(virtualId)) code = transformSync(`${virtualId}.ts`, code, {
479
+ lang: "ts",
480
+ sourcemap: false
481
+ }).code;
482
+ return code;
483
+ }
484
+ },
485
+ config() {
486
+ return {
487
+ appType: "custom",
488
+ optimizeDeps: { exclude: [
489
+ "ubean",
490
+ ...VIRTUAL_IDS,
491
+ ...HASH_IDS
492
+ ] },
493
+ ssr: { noExternal: ["ubean"] }
494
+ };
495
+ },
496
+ transformIndexHtml(html, ctx) {
497
+ if (ctx?.path?.includes("_devtools")) return html;
498
+ let result = html;
499
+ if (!/<link\b[^>]*rel=["']icon["']/i.test(result)) result = result.replace("<head>", "<head>\n <link rel=\"icon\" href=\"/favicon.svg\" type=\"image/svg+xml\" />");
500
+ if (result.includes(CLIENT_ENTRY_URL) || result.includes(VIRTUAL_CLIENT)) return result;
501
+ return result.replace("</body>", ` <script type="module" src="${CLIENT_ENTRY_URL}"><\/script>\n</body>`);
502
+ },
503
+ configureServer(server) {
504
+ const watchDirs = [
505
+ "pages",
506
+ "layouts",
507
+ "app"
508
+ ];
509
+ for (const dir of watchDirs) server.watcher.add(join(srcDir, dir));
510
+ async function handleFileChange(file) {
511
+ const rel = file.replace(`${srcDir}/`, "");
512
+ const isAppFile = /^app(\.(server|client))?\.(ts|js|mjs|mts)$/.test(rel);
513
+ const isServerFile = /^server(\.(dev|prod))?\.(ts|js|mjs|mts)$/.test(rel);
514
+ const isMarkdownFile = new RegExp(`\\.(${mdExtensions.join("|")})$`).test(rel);
515
+ if (isAppFile || isServerFile || watchDirs.some((d) => rel.startsWith(`${d}/`)) || isMarkdownFile) {
516
+ await scanAndRegister();
517
+ for (const vid of VIRTUAL_IDS) {
518
+ const mod = server.moduleGraph.getModuleById(toResolvedVirtualId(vid));
519
+ if (mod) server.moduleGraph.invalidateModule(mod);
520
+ }
521
+ server.ws.send({ type: "full-reload" });
522
+ }
523
+ }
524
+ server.watcher.on("add", handleFileChange);
525
+ server.watcher.on("unlink", handleFileChange);
526
+ server.watcher.on("change", handleFileChange);
527
+ }
528
+ }];
529
+ if (markdownEnabled) {
530
+ const markdownOptions = {
531
+ ...ubeanConfig.markdown?.markdownExit,
532
+ html: true
533
+ };
534
+ plugins.push(Markdown({
535
+ markdownOptions,
536
+ wrapperClasses: "markdown-body",
537
+ headEnabled: true,
538
+ headField: "head"
539
+ }));
540
+ }
541
+ if (autoImportEnabled) plugins.push(AutoImport({
542
+ imports: [UBEAN_CLIENT_PRESET, UBEAN_SERVER_PRESET],
543
+ dirs: composablesDirs,
544
+ dts: join(dtsDir, "auto-imports.d.ts"),
545
+ vueTemplate: true,
546
+ eslintrc: { enabled: false }
547
+ }));
548
+ const UBEAN_BUILTIN_COMPONENTS = [
549
+ "Link",
550
+ "Head",
551
+ "PageView"
552
+ ];
553
+ function ubeanComponentsResolver(componentName) {
554
+ if (UBEAN_BUILTIN_COMPONENTS.includes(componentName)) return {
555
+ name: componentName,
556
+ from: "ubean/runtime/vue"
557
+ };
558
+ }
559
+ const allResolvers = [ubeanComponentsResolver, ...getComponentResolvers()];
560
+ if (componentAutoImportEnabled) {
561
+ const extensions = ["vue"];
562
+ const includePatterns = [/\.vue$/, /\.vue\?vue/];
563
+ if (markdownEnabled && markdownComponentsAutoImport) {
564
+ extensions.push(...mdExtensions);
565
+ includePatterns.push(/\.md$/);
566
+ if (mdxEnabled) includePatterns.push(/\.mdx$/);
567
+ }
568
+ plugins.push(Components({
569
+ dirs: componentsDirs,
570
+ extensions,
571
+ include: includePatterns,
572
+ directoryAsNamespace,
573
+ dts: join(dtsDir, "components.d.ts"),
574
+ deep: true,
575
+ resolvers: allResolvers
576
+ }));
577
+ } else plugins.push(Components({
578
+ dts: true,
579
+ resolvers: allResolvers
580
+ }));
581
+ return plugins;
582
+ }
583
+ //#endregion
584
+ export { VUE_PLUGIN_INCLUDE, createClientEntryVirtualModule, createServerEntryVirtualModule, createVueAppEntryVirtualModule, createVuePagesVirtualModule, ubeanVuePlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubean/vite",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Vue-specific Vite plugin for ubean (ubeanVuePlugin, virtual modules: pages, app, server, client-entry)",
5
5
  "files": [
6
6
  "dist"
@@ -21,12 +21,12 @@
21
21
  "unplugin-auto-import": "21.0.0",
22
22
  "unplugin-vue-components": "^32.1.0",
23
23
  "unplugin-vue-markdown": "^32.0.0",
24
- "@ubean/auto-imports": "0.1.2",
25
- "@ubean/build": "0.1.2",
26
- "@ubean/config": "0.1.2",
27
- "@ubean/routing": "0.1.2",
28
- "@ubean/runtime": "0.1.2",
29
- "@ubean/markdown": "0.1.2"
24
+ "@ubean/build": "0.1.3",
25
+ "@ubean/auto-imports": "0.1.3",
26
+ "@ubean/markdown": "0.1.3",
27
+ "@ubean/routing": "0.1.3",
28
+ "@ubean/runtime": "0.1.3",
29
+ "@ubean/config": "0.1.3"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^26.1.1",