@ubean/build 0.2.2 → 0.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.
@@ -0,0 +1,694 @@
1
+ import { a as useVirtualRegistry, n as defineVirtualModule } from "./virtual-registry-BWkaQHXN.js";
2
+ import { c as getCssImports, i as ssrSingletonDevPolicy, s as getComponentResolvers } from "./ssr-singleton-CEQi_H6-.js";
3
+ import { f as UBEAN_CLIENT_PRESET, p as UBEAN_SERVER_PRESET } from "./codegen-D6ewf11I.js";
4
+ import { createRequire } from "node:module";
5
+ import { join } from "pathe";
6
+ import { scanProject } from "@ubean/scan";
7
+ import { getVueLocaleParam } from "@ubean/i18n";
8
+ import { transformWithOxc } from "vite";
9
+ import Components from "unplugin-vue-components/vite";
10
+ import Markdown from "unplugin-vue-markdown/vite";
11
+ import AutoImport from "unplugin-auto-import/vite";
12
+ import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
13
+ import { getColorModeScript, getPartyTownScript, resolveColorModeConfig, resolvePartyTownConfig } from "@ubean/client";
14
+ import { ubeanMdxPlugin } from "@ubean/markdown";
15
+ import { renderFaviconLink } from "@ubean/pages";
16
+ import { generatePagesModuleSource } from "@ubean/vue/vite";
17
+ //#region src/vue-virtual-modules.ts
18
+ /**
19
+ * 页面虚拟模块(`virtual:ubean-pages`)。
20
+ *
21
+ * 生成逻辑已沉淀到 `@ubean/vue/vite` 的 `generatePagesModuleSource`
22
+ * (页面路由唯一所有者,与精简内核 `virtual:ubean-vue-routes` 共用同一
23
+ * 生成器)。此处仅保留框架层的模块 ID 注册(`defineVirtualModule`)与
24
+ * 兼容签名;route meta 相比旧版额外携带 `matchers` / `transition` /
25
+ * `requiresAuth` / `head` / `definePage({ meta })` 透传字段(超集,
26
+ * 使 `createMatcherGuard()` 与 SPA head guard 在框架模式下同样可用)。
27
+ */
28
+ function createVuePagesVirtualModule(pages, layouts, notFoundPage, loadingPage, errorPage, localeVueParam) {
29
+ return defineVirtualModule("virtual:ubean-pages", () => generatePagesModuleSource({
30
+ pages,
31
+ layouts,
32
+ notFoundPage,
33
+ loadingPage,
34
+ errorPage
35
+ }, { vueParam: localeVueParam }));
36
+ }
37
+ const EMPTY_APP_ENTRY = {
38
+ shared: { exists: false },
39
+ server: { exists: false },
40
+ client: { exists: false }
41
+ };
42
+ function createVueAppEntryVirtualModule(appEntry = EMPTY_APP_ENTRY) {
43
+ return defineVirtualModule("virtual:ubean-app", () => {
44
+ const hasSharedApp = appEntry.shared.exists;
45
+ const hasServerApp = appEntry.server.exists;
46
+ const hasClientApp = appEntry.client.exists;
47
+ return `${`
48
+ // Auto-generated by ubean - do not edit
49
+ /* eslint-disable */
50
+ ${hasSharedApp ? `import _sharedApp from ${JSON.stringify(appEntry.shared.fullPath)};` : "const _sharedApp = null;"}
51
+ ${hasServerApp ? `import _serverApp from ${JSON.stringify(appEntry.server.fullPath)};` : "const _serverApp = null;"}
52
+ ${hasClientApp ? `import _clientApp from ${JSON.stringify(appEntry.client.fullPath)};` : "const _clientApp = null;"}
53
+
54
+ import {
55
+ createUbeanClientApp,
56
+ createUbeanSSRApp,
57
+ usePage,
58
+ useRouter,
59
+ useHead,
60
+ useSeoMeta,
61
+ Link,
62
+ Head,
63
+ getInitialPageData,
64
+ getInitialState,
65
+ defineApp,
66
+ applyAppConfig,
67
+ createDefaultAppConfig,
68
+ createClientHead,
69
+ createServerHead,
70
+ hydrateIslands,
71
+ hasPendingIslands,
72
+ scheduleIslandHydration,
73
+ configureI18nRuntime
74
+ } from 'ubean/client';
75
+
76
+ import { i18nConfig as _i18nConfig, loadLocale as _loadLocale } from 'ubean:locales';
77
+
78
+ if (_i18nConfig && _i18nConfig.enabled !== false) {
79
+ configureI18nRuntime({
80
+ config: {
81
+ defaultLocale: _i18nConfig.defaultLocale,
82
+ locales: (_i18nConfig.locales || []).map(l => typeof l === 'string' ? l : l.code),
83
+ strategy: _i18nConfig.strategy || 'prefix_except_default',
84
+ fallbackLocale: _i18nConfig.fallbackLocale || _i18nConfig.defaultLocale,
85
+ cookieName: (_i18nConfig.detectBrowserLanguage && _i18nConfig.detectBrowserLanguage.cookieName) || 'ubean_locale',
86
+ baseUrl: _i18nConfig.baseUrl || '',
87
+ vueI18n: _i18nConfig.vueI18n
88
+ },
89
+ loadLocale: _loadLocale,
90
+ locales: (_i18nConfig.locales || []).map(l => typeof l === 'string' ? { code: l } : l)
91
+ });
92
+ }
93
+
94
+ export {
95
+ createUbeanClientApp,
96
+ createUbeanSSRApp,
97
+ usePage,
98
+ useRouter,
99
+ useHead,
100
+ useSeoMeta,
101
+ Link,
102
+ Head,
103
+ getInitialPageData,
104
+ getInitialState,
105
+ defineApp,
106
+ applyAppConfig,
107
+ createDefaultAppConfig
108
+ };
109
+
110
+ import {
111
+ routes,
112
+ resolvePageComponent,
113
+ resolveLayoutComponent,
114
+ defaultLayout,
115
+ pageNames,
116
+ layoutNames,
117
+ resolveLoadingComponent,
118
+ resolveErrorComponent,
119
+ hasNotFoundPage,
120
+ hasErrorPage
121
+ } from 'virtual:ubean-pages';
122
+
123
+ export {
124
+ routes,
125
+ resolvePageComponent,
126
+ resolveLayoutComponent,
127
+ defaultLayout,
128
+ pageNames,
129
+ layoutNames,
130
+ resolveLoadingComponent,
131
+ resolveErrorComponent,
132
+ hasNotFoundPage,
133
+ hasErrorPage
134
+ };
135
+
136
+ function _mergeAppConfig(base, ...configs) {
137
+ const result = { ...base };
138
+ for (const cfg of configs) {
139
+ if (!cfg) continue;
140
+ if (cfg.plugins) result.plugins = [...(result.plugins || []), ...cfg.plugins];
141
+ if (cfg.globalComponents) result.globalComponents = { ...(result.globalComponents || {}), ...cfg.globalComponents };
142
+ if (cfg.provides) result.provides = { ...(result.provides || {}), ...cfg.provides };
143
+ if (cfg.head) result.head = { ...(result.head || {}), ...cfg.head };
144
+ if (cfg.rootId) result.rootId = cfg.rootId;
145
+ if (cfg.rootAttrs) result.rootAttrs = { ...(result.rootAttrs || {}), ...cfg.rootAttrs };
146
+ if (cfg.onAppCreated) result.onAppCreated = cfg.onAppCreated;
147
+ if (cfg.onClientReady) result.onClientReady = cfg.onClientReady;
148
+ if (cfg.errorComponent) result.errorComponent = cfg.errorComponent;
149
+ if (cfg.loadingComponent) result.loadingComponent = cfg.loadingComponent;
150
+ if (cfg.viewTransitions !== undefined) result.viewTransitions = cfg.viewTransitions;
151
+ if (cfg.serializeState) result.serializeState = cfg.serializeState;
152
+ if (cfg.hydrateState) result.hydrateState = cfg.hydrateState;
153
+ }
154
+ // router.setup:累加语义 — shared 和 client/server 各自定义的 setup 都会执行
155
+ // (顺序:shared 先,client/server 后)。这样 shared 可放通用守卫(如埋点),
156
+ // client/server 可放环境专用守卫(如 SSR 鉴权)。
157
+ const setups = [];
158
+ for (const cfg of [base, ...configs]) {
159
+ if (cfg?.router?.setup) setups.push(cfg.router.setup);
160
+ }
161
+ if (setups.length === 1) {
162
+ result.router = { setup: setups[0] };
163
+ } else if (setups.length > 1) {
164
+ result.router = {
165
+ setup: (router) => {
166
+ for (const s of setups) s(router);
167
+ }
168
+ };
169
+ }
170
+ return result;
171
+ }
172
+
173
+ export function resolveAppConfig(mode) {
174
+ const base = createDefaultAppConfig();
175
+ if (!_sharedApp && !_serverApp && !_clientApp) return base;
176
+
177
+ const sharedCfg = typeof _sharedApp === 'function' ? _sharedApp() : _sharedApp;
178
+ const serverCfg = mode === 'server' && _serverApp ? (typeof _serverApp === 'function' ? _serverApp() : _serverApp) : null;
179
+ const clientCfg = mode === 'client' && _clientApp ? (typeof _clientApp === 'function' ? _clientApp() : _clientApp) : null;
180
+
181
+ const merged = _mergeAppConfig(base, sharedCfg, mode === 'server' ? serverCfg : clientCfg);
182
+
183
+ // onAppCreated: shared config is the default; server/client-specific overrides it if present.
184
+ if (sharedCfg?.onAppCreated) merged.onAppCreated = sharedCfg.onAppCreated;
185
+ if (mode === 'client' && clientCfg?.onClientReady) merged.onClientReady = clientCfg.onClientReady;
186
+ else if (mode === 'server' && serverCfg?.onAppCreated) merged.onAppCreated = serverCfg.onAppCreated;
187
+
188
+ return merged;
189
+ }
190
+
191
+ export async function createApp() {
192
+ const config = resolveAppConfig('client');
193
+ const head = createClientHead();
194
+
195
+ // Push global app head (from defineApp) as defaults before any page-level head.
196
+ if (config.head) {
197
+ const headInput = {};
198
+ if (config.head.title) headInput.title = config.head.title;
199
+ if (config.head.htmlAttrs) headInput.htmlAttrs = config.head.htmlAttrs;
200
+ if (config.head.bodyAttrs) headInput.bodyAttrs = config.head.bodyAttrs;
201
+ if (config.head.meta) headInput.meta = config.head.meta;
202
+ if (config.head.link) headInput.link = config.head.link;
203
+ if (config.head.script) headInput.script = config.head.script;
204
+ head.push(headInput);
205
+ }
206
+
207
+ // Resolve the auto-detected loading/error components from pages/loading.vue
208
+ // and pages/error.vue. These loaders return Promises (dynamic imports), so
209
+ // they MUST be awaited — otherwise 'resolveComp' markRaw's the unresolved
210
+ // Promise object and passes it to <Suspense>/<ErrorBoundary> as the
211
+ // 'component' prop, causing "Hydration node mismatch: server <div> vs
212
+ // client Symbol(v-fgt)" (rendering a Promise produces a fragment).
213
+ // defineApp({ loadingComponent/errorComponent }) takes priority.
214
+ const loadingComponent = config.loadingComponent || (await resolveLoadingComponent()) || undefined;
215
+ const errorComponent = config.errorComponent || (await resolveErrorComponent()) || undefined;
216
+
217
+ const initialPage = getInitialPageData();
218
+ const instance = createUbeanClientApp({
219
+ routes,
220
+ resolveLayoutComponent,
221
+ defaultLayout,
222
+ head,
223
+ viewTransitions: config.viewTransitions,
224
+ initialPage: initialPage || undefined,
225
+ hydrate: !!initialPage,
226
+ routerSetup: config.router?.setup,
227
+ loadingComponent,
228
+ errorComponent
229
+ });
230
+
231
+ applyAppConfig(instance.app, config, 'client');
232
+
233
+ if (config.onAppCreated) config.onAppCreated(instance.app);
234
+
235
+ // SSR 状态水合:在 applyAppConfig(注册插件)之后、mount 之前调用。
236
+ // getInitialState() 从 DOM 的 __UBEAN_STATE__ script 读取服务端
237
+ // serializeState 产生的状态对象(如 Pinia 的 state)。
238
+ // 必须在 mount 前执行,否则 store 已初始化为默认值,水合无效。
239
+ if (config.hydrateState) {
240
+ const state = getInitialState();
241
+ config.hydrateState(instance.app, state);
242
+ }
243
+
244
+ const mountApp = () => {
245
+ instance.app.mount('#' + (config.rootId || 'app'));
246
+ if (config.onClientReady) {
247
+ config.onClientReady(instance.app);
248
+ }
249
+ // Islands: first mount always waits two rAFs so Vue's patch finishes.
250
+ // SPA afterEach waits one rAF, then skips the second when no pending islands.
251
+ var appRoot = function () {
252
+ return document.getElementById('app') || undefined;
253
+ };
254
+ var hydrateNow = function () {
255
+ hydrateIslands({ appContext: instance.app, root: appRoot() });
256
+ };
257
+ var pendingIslands = function () {
258
+ return hasPendingIslands(appRoot());
259
+ };
260
+ scheduleIslandHydration({
261
+ requestAnimationFrame: requestAnimationFrame,
262
+ hasPending: pendingIslands,
263
+ hydrate: hydrateNow,
264
+ forceDoubleFrame: true
265
+ });
266
+ instance.router.afterEach(function () {
267
+ scheduleIslandHydration({
268
+ requestAnimationFrame: requestAnimationFrame,
269
+ hasPending: pendingIslands,
270
+ hydrate: hydrateNow
271
+ });
272
+ });
273
+ };
274
+
275
+ // When hydrating SSR content, must wait for router to be ready before mounting,
276
+ // otherwise RouterView has no matched route and causes hydration mismatch.
277
+ if (initialPage) {
278
+ instance.router.isReady().then(mountApp);
279
+ } else {
280
+ mountApp();
281
+ }
282
+
283
+ return instance;
284
+ }
285
+
286
+ export async function createSSRApp(initialPage) {
287
+ const config = resolveAppConfig('server');
288
+ const head = createServerHead();
289
+
290
+ // SSR doesn't need Suspense fallback (server resolves async synchronously),
291
+ // but we still pass it for consistency. Await the loaders so resolved
292
+ // Component values (not Promises) are passed to createUbeanSSRApp.
293
+ const loadingComponent = config.loadingComponent || (await resolveLoadingComponent()) || undefined;
294
+
295
+ const errorComponent = config.errorComponent || (await resolveErrorComponent()) || undefined;
296
+
297
+ const { app, router } = createUbeanSSRApp(initialPage, {
298
+ routes,
299
+ resolveLayoutComponent,
300
+ defaultLayout,
301
+ head,
302
+ routerSetup: config.router?.setup,
303
+ loadingComponent,
304
+ errorComponent
305
+ });
306
+
307
+ applyAppConfig(app, config, 'server');
308
+
309
+ if (config.onAppCreated) config.onAppCreated(app);
310
+
311
+ return { app, router, head, config };
312
+ }
313
+
314
+ export const rootId = 'app';
315
+ `.trim()}\n`;
316
+ });
317
+ }
318
+ function createClientEntryVirtualModule() {
319
+ return defineVirtualModule("virtual:ubean-client-entry", () => {
320
+ return `${`
321
+ // Auto-generated by ubean client entry - do not edit
322
+ /* eslint-disable */
323
+ ${getCssImports().map((css) => `import ${JSON.stringify(css)};`).join("\n")}
324
+ import { createApp } from 'virtual:ubean-app';
325
+
326
+ createApp();
327
+ `.trim()}\n`;
328
+ });
329
+ }
330
+ const EMPTY_SERVER_ENTRY = {
331
+ shared: { exists: false },
332
+ dev: { exists: false },
333
+ prod: { exists: false }
334
+ };
335
+ function createServerEntryVirtualModule(serverEntry = EMPTY_SERVER_ENTRY) {
336
+ return defineVirtualModule("virtual:ubean-server", () => {
337
+ const hasShared = serverEntry.shared.exists;
338
+ const hasDev = serverEntry.dev.exists;
339
+ const hasProd = serverEntry.prod.exists;
340
+ return `${`
341
+ // Auto-generated by ubean server entry - do not edit
342
+ /* eslint-disable */
343
+ ${hasShared ? `import _sharedServer from ${JSON.stringify(serverEntry.shared.fullPath)};` : "const _sharedServer = null;"}
344
+ ${hasDev ? `import _devServer from ${JSON.stringify(serverEntry.dev.fullPath)};` : "const _devServer = null;"}
345
+ ${hasProd ? `import _prodServer from ${JSON.stringify(serverEntry.prod.fullPath)};` : "const _prodServer = null;"}
346
+
347
+ import {
348
+ defineServer,
349
+ createDefaultServerConfig,
350
+ mergeServerConfigs
351
+ } from 'ubean/runtime/app';
352
+
353
+ export {
354
+ defineServer,
355
+ createDefaultServerConfig,
356
+ mergeServerConfigs
357
+ };
358
+
359
+ /**
360
+ * Resolve the user's server config for the given mode ('dev' | 'prod').
361
+ * Merges shared config with mode-specific config. Returns an empty
362
+ * config when no server entry file exists.
363
+ */
364
+ export function resolveServerConfig(mode) {
365
+ const base = createDefaultServerConfig();
366
+
367
+ if (!_sharedServer && !_devServer && !_prodServer) return base;
368
+
369
+ const sharedCfg = typeof _sharedServer === 'function' ? _sharedServer() : _sharedServer;
370
+ const modeCfg = mode === 'dev'
371
+ ? (typeof _devServer === 'function' ? _devServer() : _devServer)
372
+ : (typeof _prodServer === 'function' ? _prodServer() : _prodServer);
373
+
374
+ return mergeServerConfigs(base, sharedCfg, modeCfg);
375
+ }
376
+ `.trim()}\n`;
377
+ });
378
+ }
379
+ //#endregion
380
+ //#region src/vue-plugin.ts
381
+ const VUE_PLUGIN_INCLUDE = [/\.vue$/, /\.md$/];
382
+ const VIRTUAL_PAGES = "virtual:ubean-pages";
383
+ const VIRTUAL_APP = "virtual:ubean-app";
384
+ const VIRTUAL_CLIENT = "virtual:ubean-client-entry";
385
+ const VIRTUAL_SERVER = "virtual:ubean-server";
386
+ const CLIENT_ENTRY_URL = `/@id/${VIRTUAL_CLIENT}`;
387
+ const TS_VIRTUAL_IDS = [
388
+ VIRTUAL_PAGES,
389
+ VIRTUAL_APP,
390
+ VIRTUAL_SERVER
391
+ ];
392
+ const VIRTUAL_IDS = [
393
+ VIRTUAL_PAGES,
394
+ VIRTUAL_APP,
395
+ VIRTUAL_CLIENT,
396
+ VIRTUAL_SERVER
397
+ ];
398
+ const HASH_ID_TO_VIRTUAL = {
399
+ "#ubean-pages": VIRTUAL_PAGES,
400
+ "#ubean-app": VIRTUAL_APP,
401
+ "#ubean-client-entry": VIRTUAL_CLIENT,
402
+ "#ubean-server": VIRTUAL_SERVER
403
+ };
404
+ const NULL_PREFIX = "\0";
405
+ const VIRTUAL_EXT = ".ts";
406
+ function toResolvedVirtualId(virtualId) {
407
+ return NULL_PREFIX + virtualId + VIRTUAL_EXT;
408
+ }
409
+ function parseResolvedVirtualId(resolvedId) {
410
+ if (!resolvedId.startsWith(NULL_PREFIX)) return void 0;
411
+ const withoutPrefix = resolvedId.slice(1);
412
+ if (!withoutPrefix.endsWith(VIRTUAL_EXT)) return void 0;
413
+ const virtualId = withoutPrefix.slice(0, -3);
414
+ return VIRTUAL_IDS.includes(virtualId) ? virtualId : void 0;
415
+ }
416
+ function localeVueParamFromConfig(config) {
417
+ if (config.i18n?.enabled === false) return void 0;
418
+ const codes = (config.i18n?.locales || []).map((l) => l.code);
419
+ if (codes.length === 0) return void 0;
420
+ return getVueLocaleParam({
421
+ defaultLocale: config.i18n.defaultLocale,
422
+ locales: codes,
423
+ strategy: config.i18n.strategy
424
+ }) || void 0;
425
+ }
426
+ function ubeanVite(options) {
427
+ const { config: ubeanConfig } = options;
428
+ const virtualRegistry = useVirtualRegistry();
429
+ const srcDir = join(ubeanConfig.rootDir, ubeanConfig.srcDir);
430
+ const dtsDir = join(ubeanConfig.rootDir, ".ubean");
431
+ const markdownEnabled = ubeanConfig.markdown?.enabled !== false;
432
+ const mdxEnabled = ubeanConfig.markdown?.mdx === true;
433
+ const autoImportEnabled = ubeanConfig.imports.autoImport !== false;
434
+ const componentAutoImportEnabled = ubeanConfig.components.autoImport !== false;
435
+ const markdownComponentsAutoImport = ubeanConfig.markdown?.components?.autoImport !== false;
436
+ const directoryAsNamespace = ubeanConfig.components.directoryAsNamespace ?? false;
437
+ const composablesDirName = ubeanConfig.dir.composables || "composables";
438
+ const componentsDirName = ubeanConfig.dir.components || "components";
439
+ const composablesDirs = [join(srcDir, composablesDirName), ...ubeanConfig.imports.dirs || []];
440
+ const componentsDirs = [join(srcDir, componentsDirName), ...ubeanConfig.components.dirs || []];
441
+ const mdExtensions = mdxEnabled ? ["md", "mdx"] : ["md"];
442
+ function getVirtualModule(virtualId) {
443
+ return virtualRegistry.getModules().find((m) => m.id === virtualId);
444
+ }
445
+ async function loadVirtualModule(virtualId) {
446
+ const mod = getVirtualModule(virtualId);
447
+ if (!mod) return void 0;
448
+ return mod.load();
449
+ }
450
+ async function scanAndRegister() {
451
+ const result = await scanProject({
452
+ cwd: ubeanConfig.rootDir,
453
+ srcDir: ubeanConfig.srcDir,
454
+ dirs: ubeanConfig.dir,
455
+ ignore: ubeanConfig.scanOptions?.ignore
456
+ });
457
+ virtualRegistry.register(createVuePagesVirtualModule(result.pages, result.layouts, result.notFoundPage, result.loadingPage, result.errorPage, localeVueParamFromConfig(ubeanConfig)));
458
+ virtualRegistry.register(createVueAppEntryVirtualModule(result.appEntry));
459
+ virtualRegistry.register(createServerEntryVirtualModule(result.serverEntry));
460
+ virtualRegistry.register(createClientEntryVirtualModule());
461
+ }
462
+ const HASH_IDS = Object.keys(HASH_ID_TO_VIRTUAL);
463
+ const plugins = [{
464
+ name: "ubean:vue",
465
+ enforce: "pre",
466
+ async buildStart() {
467
+ await scanAndRegister();
468
+ },
469
+ resolveId(id, importer, opts) {
470
+ if (HASH_ID_TO_VIRTUAL[id]) return toResolvedVirtualId(HASH_ID_TO_VIRTUAL[id]);
471
+ if (VIRTUAL_IDS.includes(id)) return toResolvedVirtualId(id);
472
+ if (id === "@ubean/i18n" && !opts?.ssr) return this.resolve("@ubean/i18n/browser", importer, {
473
+ skipSelf: true,
474
+ ...opts
475
+ });
476
+ },
477
+ async load(id) {
478
+ const virtualId = parseResolvedVirtualId(id);
479
+ if (virtualId) {
480
+ let code = await loadVirtualModule(virtualId);
481
+ if (code && TS_VIRTUAL_IDS.includes(virtualId)) code = (await transformWithOxc(code, `${virtualId}.ts`)).code;
482
+ return code;
483
+ }
484
+ },
485
+ config() {
486
+ const require = createRequire(import.meta.url);
487
+ let vueI18nEntry = "vue-i18n";
488
+ let intlifyCoreEntry = "@intlify/core";
489
+ let intlifyBaseEntry = "@intlify/core-base";
490
+ try {
491
+ vueI18nEntry = require.resolve("vue-i18n/dist/vue-i18n.esm-bundler.js");
492
+ } catch {
493
+ try {
494
+ vueI18nEntry = require.resolve("vue-i18n");
495
+ } catch {}
496
+ }
497
+ try {
498
+ intlifyCoreEntry = require.resolve("@intlify/core/dist/core.node.mjs");
499
+ } catch {}
500
+ try {
501
+ intlifyBaseEntry = require.resolve("@intlify/core-base/dist/core-base.mjs");
502
+ } catch {}
503
+ const singleton = ssrSingletonDevPolicy();
504
+ return {
505
+ appType: "custom",
506
+ resolve: {
507
+ ...singleton.resolve,
508
+ alias: {
509
+ "vue-i18n": vueI18nEntry,
510
+ "@intlify/core": intlifyCoreEntry,
511
+ "@intlify/core-base": intlifyBaseEntry
512
+ }
513
+ },
514
+ optimizeDeps: {
515
+ exclude: [
516
+ ...singleton.optimizeDeps.exclude,
517
+ ...VIRTUAL_IDS,
518
+ ...HASH_IDS
519
+ ],
520
+ include: singleton.optimizeDeps.include
521
+ },
522
+ ssr: singleton.ssr
523
+ };
524
+ },
525
+ transformIndexHtml(html, ctx) {
526
+ if (ctx?.path?.includes("_devtools")) return html;
527
+ let result = html;
528
+ const colorModeConfig = ubeanConfig.colorMode;
529
+ if (colorModeConfig !== false) {
530
+ const script = getColorModeScript(resolveColorModeConfig(colorModeConfig));
531
+ result = result.replace("<head>", `<head>\n ${script}`);
532
+ }
533
+ const partyTownConfig = ubeanConfig.partyTown;
534
+ if (partyTownConfig !== false && partyTownConfig !== void 0) {
535
+ const resolved = resolvePartyTownConfig(partyTownConfig === true ? { enabled: true } : partyTownConfig);
536
+ if (resolved.enabled) {
537
+ const script = getPartyTownScript(resolved);
538
+ if (script) result = result.replace("</head>", ` ${script}\n</head>`);
539
+ }
540
+ }
541
+ if (!/<link\b[^>]*rel=["']icon["']/i.test(result)) {
542
+ const faviconLink = renderFaviconLink(ubeanConfig.favicon ?? void 0);
543
+ if (faviconLink) result = result.replace("<head>", `<head>\n ${faviconLink}`);
544
+ }
545
+ if (result.includes(CLIENT_ENTRY_URL) || result.includes(VIRTUAL_CLIENT)) return result;
546
+ return result.replace("</body>", ` <script type="module" src="${CLIENT_ENTRY_URL}"><\/script>\n</body>`);
547
+ },
548
+ configureServer(server) {
549
+ const watchDirs = [
550
+ "pages",
551
+ "layouts",
552
+ "app"
553
+ ];
554
+ for (const dir of watchDirs) server.watcher.add(join(srcDir, dir));
555
+ async function handleFileChange(file) {
556
+ const rel = file.replace(`${srcDir}/`, "");
557
+ const isAppFile = /^app(\.(server|client))?\.(ts|js|mjs|mts)$/.test(rel);
558
+ const isServerFile = /^server(\.(dev|prod))?\.(ts|js|mjs|mts)$/.test(rel);
559
+ const isMarkdownFile = new RegExp(`\\.(${mdExtensions.join("|")})$`).test(rel);
560
+ if (isAppFile || isServerFile || watchDirs.some((d) => rel.startsWith(`${d}/`)) || isMarkdownFile) {
561
+ await scanAndRegister();
562
+ for (const vid of VIRTUAL_IDS) {
563
+ const mod = server.moduleGraph.getModuleById(toResolvedVirtualId(vid));
564
+ if (mod) server.moduleGraph.invalidateModule(mod);
565
+ }
566
+ server.ws.send({ type: "full-reload" });
567
+ }
568
+ }
569
+ server.watcher.on("add", handleFileChange);
570
+ server.watcher.on("unlink", handleFileChange);
571
+ server.watcher.on("change", handleFileChange);
572
+ }
573
+ }];
574
+ if (ubeanConfig.i18n?.enabled !== false) plugins.push(VueI18nPlugin({
575
+ include: [join(srcDir, "locales/**")],
576
+ ssr: true,
577
+ compositionOnly: true,
578
+ runtimeOnly: false
579
+ }));
580
+ if (markdownEnabled) {
581
+ const markdownOptions = {
582
+ ...ubeanConfig.markdown?.markdownExit,
583
+ html: true
584
+ };
585
+ plugins.push(Markdown({
586
+ markdownOptions,
587
+ wrapperClasses: ubeanConfig.markdown?.wrapperClass ?? "markdown-body",
588
+ headEnabled: true,
589
+ headField: "head"
590
+ }));
591
+ }
592
+ if (mdxEnabled) plugins.push(ubeanMdxPlugin({
593
+ remarkPlugins: ubeanConfig.markdown?.remarkPlugins || [],
594
+ rehypePlugins: ubeanConfig.markdown?.rehypePlugins || []
595
+ }));
596
+ if (autoImportEnabled) plugins.push(AutoImport({
597
+ imports: [UBEAN_CLIENT_PRESET, UBEAN_SERVER_PRESET],
598
+ dirs: composablesDirs,
599
+ dts: join(dtsDir, "auto-imports.d.ts"),
600
+ vueTemplate: true,
601
+ eslintrc: { enabled: false }
602
+ }));
603
+ const UBEAN_BUILTIN_COMPONENTS = [
604
+ "Link",
605
+ "Head",
606
+ "PageView"
607
+ ];
608
+ function ubeanComponentsResolver(componentName) {
609
+ if (UBEAN_BUILTIN_COMPONENTS.includes(componentName)) return {
610
+ name: componentName,
611
+ from: "ubean/client"
612
+ };
613
+ }
614
+ const dynamicResolvers = [ubeanComponentsResolver, (name) => {
615
+ for (const resolver of getComponentResolvers()) {
616
+ const result = typeof resolver === "function" ? resolver(name) : resolver.resolve(name);
617
+ if (result) return result;
618
+ }
619
+ }];
620
+ if (componentAutoImportEnabled) {
621
+ const extensions = ["vue"];
622
+ const includePatterns = [/\.vue$/, /\.vue\?vue/];
623
+ if (markdownEnabled && markdownComponentsAutoImport) {
624
+ extensions.push(...mdExtensions);
625
+ includePatterns.push(/\.md$/);
626
+ if (mdxEnabled) includePatterns.push(/\.mdx$/);
627
+ }
628
+ plugins.push(Components({
629
+ dirs: componentsDirs,
630
+ extensions,
631
+ include: includePatterns,
632
+ directoryAsNamespace,
633
+ dts: join(dtsDir, "components.d.ts"),
634
+ deep: true,
635
+ resolvers: dynamicResolvers
636
+ }));
637
+ } else plugins.push(Components({
638
+ dts: true,
639
+ resolvers: dynamicResolvers
640
+ }));
641
+ const searchConfig = ubeanConfig.search;
642
+ if (searchConfig !== false && searchConfig !== void 0) plugins.push({
643
+ name: "ubean:pagefind",
644
+ apply: "build",
645
+ closeBundle() {
646
+ return runPagefindIndexing(ubeanConfig, searchConfig);
647
+ }
648
+ });
649
+ return plugins;
650
+ }
651
+ /**
652
+ * P9-26: Run the Pagefind CLI to index built HTML files.
653
+ *
654
+ * Spawns `npx pagefind --site <dir>` after the build. If the `pagefind`
655
+ * package is not installed, prints a helpful warning instead of failing.
656
+ */
657
+ async function runPagefindIndexing(ubeanConfig, searchConfig) {
658
+ const { spawn } = await import("node:child_process");
659
+ const { resolve } = await import("node:path");
660
+ const isObjectConfig = typeof searchConfig === "object";
661
+ if (!(isObjectConfig ? searchConfig.enabled !== false : true)) return;
662
+ const outDir = isObjectConfig && searchConfig.site ? searchConfig.site : "dist";
663
+ const indexPath = isObjectConfig && searchConfig.indexPath ? searchConfig.indexPath : "pagefind";
664
+ const verbose = isObjectConfig && searchConfig.verbose === true;
665
+ const args = [
666
+ "pagefind",
667
+ "--site",
668
+ resolve(ubeanConfig.rootDir, outDir),
669
+ "--output-subdir",
670
+ indexPath
671
+ ];
672
+ if (isObjectConfig && searchConfig.glob) args.push("--glob", searchConfig.glob);
673
+ if (isObjectConfig && searchConfig.excludeSelectors) for (const selector of searchConfig.excludeSelectors) args.push("--exclude-selectors", selector);
674
+ if (verbose) args.push("--verbose");
675
+ return new Promise((resolvePromise) => {
676
+ const child = spawn("npx", args, {
677
+ stdio: "inherit",
678
+ cwd: ubeanConfig.rootDir,
679
+ shell: true
680
+ });
681
+ child.on("error", (err) => {
682
+ if (err.code === "ENOENT" || /not found/i.test(err.message)) console.warn("[ubean:pagefind] Pagefind CLI not found. Install it with `pnpm add -D pagefind` to enable full-text search.");
683
+ else console.error("[ubean:pagefind] Failed to run Pagefind:", err.message);
684
+ resolvePromise();
685
+ });
686
+ child.on("exit", (code) => {
687
+ if (code === 0) console.log("[ubean:pagefind] Search index generated successfully.");
688
+ else console.warn(`[ubean:pagefind] Pagefind exited with code ${code}. Search index may be incomplete.`);
689
+ resolvePromise();
690
+ });
691
+ });
692
+ }
693
+ //#endregion
694
+ export { createVueAppEntryVirtualModule as a, createServerEntryVirtualModule as i, ubeanVite as n, createVuePagesVirtualModule as o, createClientEntryVirtualModule as r, VUE_PLUGIN_INCLUDE as t };