@ubean/build 0.5.2 → 0.6.0-beta.2

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.
@@ -1,905 +1,2 @@
1
- import { a as useVirtualRegistry } from "./virtual-registry-BWkaQHXN.js";
2
- import { a as createRoutingVirtualModule, i as createPagesVirtualModule, n as createLocalesVirtualModule, r as createMetaVirtualModule, t as createAppVirtualModule } from "./virtual-modules-vLkLkAfT.js";
3
- import { a as ssrSingletonProdOptimizeExclude, o as ssrSingletonProdSsr } from "./ssr-singleton-CEQi_H6-.js";
4
- import { n as localeVueParamFromI18n, r as serializeI18nConfig, t as ubeanPlugin } from "./vite-D2EZl_6P.js";
5
- import { a as createVueAppEntryVirtualModule, i as createServerEntryVirtualModule, n as ubeanVite, o as createVuePagesVirtualModule, r as createClientEntryVirtualModule, t as VUE_PLUGIN_INCLUDE } from "./vue-DcydGsvy.js";
6
- import { join, relative, resolve } from "pathe";
7
- import { resolveModules } from "@ubean/config";
8
- import { build } from "vite";
9
- import { getColorModeScript, resolveColorModeConfig } from "@ubean/client";
10
- import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
11
- import { existsSync } from "node:fs";
12
- import vue from "@vitejs/plugin-vue";
13
- import { ubeanIslandsPlugin } from "@ubean/islands/vite";
14
- import { isEphemeralCachePreset, resolveProductionCacheStore } from "@ubean/preset";
15
- import { getLogger } from "@ubean/shared/logger";
16
- import { findUserViteConfig } from "@ubean/shared/node";
17
- //#region src/ssg-entry.ts
18
- /**
19
- * 生成 renderer setup 代码块(`_rendererRoutes` + `_pageRenderer`)。
20
- *
21
- * 依赖外层已定义:`_pages` / `_layouts` / `pageLoaders` / `layoutLoaders` /
22
- * (notFoundCatchAll 时)`_notFoundPage`,以及 `toVueRouterLocalePath` 导入。
23
- * ssrEnabled=false 时 `_pageRenderer = null`(与 fullstack 现状一致)。
24
- */
25
- function buildRendererSetup(options) {
26
- const { ssrEnabled, mode, localeVueParam, notFoundCatchAll } = options;
27
- if (!ssrEnabled) return `
28
- // --- SSR disabled (mode=${mode}) ---
29
- // pageRenderer is null; page requests fall back to a client-only HTML shell.
30
- const _pageRenderer = null;`;
31
- return `
32
- // --- SSR renderer setup ---
33
- const _defaultLayout = _layouts.find(l => l.isDefault)?.name || null;
34
-
35
- // Same i18n locale param as the client virtual:ubean-pages route table, so
36
- // language-prefixed URLs (e.g. /zh/playground) match the real page on SSR too.
37
- const _localeVueParam = ${JSON.stringify(localeVueParam)};
38
-
39
- const _rendererRoutes = _pages.map(p => {
40
- // For reuse routes, load the target page's module (the .reuse.ts file
41
- // only contains definePage metadata, not a Vue component).
42
- const _targetPage = p.isReuse && p.reuseTarget ? _pages.find(tp => tp.name === p.reuseTarget) : undefined;
43
- const _loaderKey = _targetPage?.relativePath || p.relativePath;
44
- return {
45
- path: _localeVueParam
46
- ? toVueRouterLocalePath(p.route.replace(/\\*\\*:(\\w[\\w-]*)/g, ':$1(.*)*'), _localeVueParam)
47
- : p.route.replace(/\\*\\*:(\\w[\\w-]*)/g, ':$1(.*)*'),
48
- name: p.name,
49
- component: async () => {
50
- const loader = pageLoaders[_loaderKey];
51
- if (!loader) throw new Error('Page loader not found: ' + _loaderKey);
52
- const mod = await loader();
53
- return mod.default || mod;
54
- },
55
- meta: {
56
- layout: p.layout === false ? false : p.layout || _defaultLayout,
57
- pageName: p.name,
58
- cache: p.cache === true ? true : undefined
59
- }
60
- };
61
- });${notFoundCatchAll ? `
62
-
63
- // 404 渲染路由:注册为静态路径 /404(而非客户端的 /:pathMatch(.*)* catch-all)。
64
- // vue-router 中静态段确定性优先于 repeatable 参数 —— 即使应用存在根 catch-all
65
- // 页面(如 [...slug].vue),/404 也稳定命中本路由,保证 404.html 产物
66
- // 渲染的是 pages/404.vue 而非被 catch-all 抢占。
67
- if (_notFoundPage) {
68
- const _nfLoaderKey = _notFoundPage.relativePath;
69
- _rendererRoutes.push({
70
- path: _localeVueParam
71
- ? toVueRouterLocalePath('/404', _localeVueParam)
72
- : '/404',
73
- name: 'NotFound',
74
- component: async () => {
75
- const loader = pageLoaders[_nfLoaderKey];
76
- if (!loader) throw new Error('Page loader not found: ' + _nfLoaderKey);
77
- const mod = await loader();
78
- return mod.default || mod;
79
- },
80
- meta: { pageName: 'NotFound' }
81
- });
82
- }` : ""}
83
-
84
- const _layoutMap = new Map();
85
- for (const l of _layouts) {
86
- _layoutMap.set(l.name, l.relativePath);
87
- }
88
-
89
- const _pageRenderer = createVueRenderer({
90
- routes: _rendererRoutes,
91
- async resolveLayoutComponent(name) {
92
- if (name === false || name == null) return null;
93
- const relPath = _layoutMap.get(name);
94
- if (!relPath) return null;
95
- const loader = layoutLoaders[relPath];
96
- if (!loader) return null;
97
- const mod = await loader();
98
- return mod.default || mod;
99
- },
100
- defaultLayout: _defaultLayout,
101
- resolveAppConfig: () => _resolveAppConfig('server')
102
- });
103
- `;
104
- }
105
- /**
106
- * 生成 client asset tags 代码块(读取 `.vite/manifest.json`)。
107
- * 依赖外层已定义 `readFileSync` / `join` / `dirname` / `fileURLToPath` 导入。
108
- */
109
- function buildAssetTagsSetup(favicon) {
110
- return `
111
- // --- Client asset tags from Vite manifest ---
112
- const __dirname = dirname(fileURLToPath(import.meta.url));
113
- let _assetTags = { css: '', preloads: '', body: '', favicon: ${JSON.stringify(favicon)} };
114
- try {
115
- const manifestPath = join(__dirname, '..', 'public', '.vite', 'manifest.json');
116
- const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
117
- const entry = Object.values(manifest).find(m => m.isEntry);
118
- if (entry) {
119
- _assetTags.body = '<script type="module" src="/' + entry.file + '"><\/script>';
120
- if (Array.isArray(entry.css)) {
121
- _assetTags.css = entry.css.map(c => '<link rel="stylesheet" href="/' + c + '">').join('\\n');
122
- }
123
- }
124
- } catch (e) {
125
- console.warn('[ubean] Failed to load client manifest:', e.message || e);
126
- }
127
- `;
128
- }
129
- /**
130
- * 生成静态 SSG entry 源码(写入 virtualDir/server-entry.mjs,由 SSR build
131
- * 产出 dist/server/entry.mjs)。
132
- *
133
- * 导出契约:
134
- * - `renderStaticPage({ pageName, params, url, locale }) → { html, statusCode }`
135
- * - `pageLoaders`(调试用)
136
- */
137
- function buildStaticSsgEntry(input) {
138
- const { pagesGlob, layoutsGlob, srcPrefix, pagesJson, layoutsJson, notFoundPageJson, localeVueParam, contentBootstrap, colorModeScript, favicon } = input;
139
- return `// Auto-generated ubean static SSG entry (mode: ssg)
140
- import { renderPage } from '@ubean/pages';
141
- import { createVueRenderer } from 'ubean/ssr';
142
- import { toVueRouterLocalePath } from '@ubean/i18n';
143
- import { i18nConfig as _i18nConfig, loadLocales as _loadLocales } from 'ubean:locales';
144
- import { resolveAppConfig as _resolveAppConfig } from 'virtual:ubean-app';
145
- ${contentBootstrap}
146
- import { readFileSync } from 'node:fs';
147
- import { join, dirname } from 'node:path';
148
- import { fileURLToPath } from 'node:url';
149
-
150
- const pageModules = import.meta.glob(${pagesGlob}, { eager: false });
151
- const layoutModules = import.meta.glob(${layoutsGlob}, { eager: false });
152
-
153
- const _srcPrefix = ${srcPrefix};
154
- function normalizeKey(p) {
155
- const prefixes = ['/pages/', '/layouts/'];
156
- for (const prefix of prefixes) {
157
- const fullPrefix = _srcPrefix + prefix;
158
- if (p.includes(fullPrefix)) {
159
- const idx = p.indexOf(fullPrefix);
160
- return p.slice(idx + fullPrefix.length);
161
- }
162
- }
163
- return p;
164
- }
165
-
166
- export const pageLoaders = {};
167
- for (const [key, loader] of Object.entries(pageModules)) {
168
- pageLoaders[normalizeKey(key)] = loader;
169
- }
170
-
171
- const layoutLoaders = {};
172
- for (const [key, loader] of Object.entries(layoutModules)) {
173
- layoutLoaders[normalizeKey(key)] = loader;
174
- }
175
-
176
- const _pages = ${pagesJson};
177
- const _layouts = ${layoutsJson};
178
- const _notFoundPage = ${notFoundPageJson};
179
- const _hasDefaultLayout = _layouts.some(l => l.isDefault);
180
- ${buildRendererSetup({
181
- ssrEnabled: true,
182
- mode: "ssg",
183
- localeVueParam,
184
- notFoundCatchAll: true
185
- })}
186
- ${buildAssetTagsSetup(favicon)}
187
- const _colorModeScript = ${JSON.stringify(colorModeScript)};
188
-
189
- // --- i18n ---
190
- const _i18nEnabled = _i18nConfig && _i18nConfig.enabled !== false;
191
- const _localeCodes = _i18nEnabled
192
- ? (_i18nConfig.locales || []).map(l => (typeof l === 'string' ? l : l.code))
193
- : [];
194
- const _defaultLocale = (_i18nEnabled && _i18nConfig.defaultLocale) || '';
195
- let _localesReady = false;
196
-
197
- async function _ensureLocales() {
198
- if (_localesReady) return;
199
- _localesReady = true;
200
- if (!_i18nEnabled || _localeCodes.length === 0) return;
201
- try {
202
- await _loadLocales();
203
- } catch (e) {
204
- console.warn('[ubean-ssg] Failed to load locales:', (e && e.message) || e);
205
- }
206
- }
207
-
208
- // 静态模式不执行 loader —— 检测到页面导出 loader 时警告一次(每页)
209
- const _loaderWarned = new Set();
210
-
211
- /**
212
- * 渲染单个静态页面(由 createStaticSsgRenderer 调用)。
213
- *
214
- * @param input.pageName 目标页面名(ScannedPageRoute.name)或 'NotFound'
215
- * @param input.params 路由参数(matchRoutePattern 提取)
216
- * @param input.url 完整 URL path(含 locale 前缀与 query)
217
- * @param input.locale 渲染语言(prefix 策略下已从 URL 提取)
218
- */
219
- export async function renderStaticPage(input) {
220
- const pageName = input && input.pageName;
221
- const page = pageName === 'NotFound' ? _notFoundPage : _pages.find(p => p.name === pageName);
222
- if (!page) return { html: '', statusCode: 404 };
223
-
224
- await _ensureLocales();
225
-
226
- // loader 检测 warn(模块加载有缓存,此处与渲染共用同一实例)
227
- const _targetPage =
228
- page.isReuse && page.reuseTarget ? _pages.find(tp => tp.name === page.reuseTarget) : undefined;
229
- const _loaderKey = (_targetPage && _targetPage.relativePath) || page.relativePath;
230
- const _modLoader = pageLoaders[_loaderKey];
231
- if (_modLoader && !_loaderWarned.has(page.name)) {
232
- try {
233
- const mod = await _modLoader();
234
- if (mod && typeof mod.loader === 'function') {
235
- _loaderWarned.add(page.name);
236
- console.warn(
237
- '[ubean-ssg] page "' + page.name + '" exports \`loader\`, which is not executed in static SSG mode'
238
- );
239
- }
240
- } catch {}
241
- }
242
-
243
- // renderContext 构造与 routes/router.ts 的页面渲染路径保持一致
244
- const renderContext = {};
245
- const locale = (input && input.locale) || _defaultLocale;
246
- if (_i18nEnabled && locale) {
247
- renderContext.locale = locale;
248
- renderContext.localeDir = 'ltr';
249
- renderContext.routing = {
250
- defaultLocale: _i18nConfig.defaultLocale || 'en',
251
- locales: _localeCodes,
252
- strategy: _i18nConfig.strategy || 'prefix_except_default'
253
- };
254
- const cookieName =
255
- _i18nConfig.detectBrowserLanguage && _i18nConfig.detectBrowserLanguage.cookieName;
256
- if (cookieName) renderContext.cookieName = cookieName;
257
- if (_i18nConfig.baseUrl) renderContext.baseUrl = _i18nConfig.baseUrl;
258
- try {
259
- const i18nMod = await import('ubean/i18n');
260
- renderContext.localeDir = i18nMod.getLocaleDir(locale);
261
- renderContext.messages = i18nMod.getLocaleMessages(locale);
262
- const fallback = i18nMod.getFallbackLocale();
263
- renderContext.fallbackLocale = fallback;
264
- if (fallback !== locale) {
265
- renderContext.fallbackMessages = i18nMod.getLocaleMessages(fallback);
266
- }
267
- renderContext.availableLocales = i18nMod.getRegisteredLocalesMeta();
268
- } catch {}
269
- }
270
-
271
- // pageObj 构造与 routes/router.ts handlePageRequest 保持一致
272
- const pageObj = {
273
- component: page.reuseTarget || page.name,
274
- props: {},
275
- params: (input && input.params) || {},
276
- url: (input && input.url) || '/',
277
- layout: page.layout === false
278
- ? false
279
- : page.layout || (page.pageMeta && page.pageMeta.layout) || (_hasDefaultLayout ? 'default' : false),
280
- errors: null,
281
- head: page.pageMeta && page.pageMeta.head
282
- };
283
-
284
- let html = await renderPage(pageObj, _assetTags, _pageRenderer, 'app', renderContext);
285
- if (_colorModeScript) {
286
- html = html.replace('<head>', '<head>\\n ' + _colorModeScript);
287
- }
288
- return { html, statusCode: 200 };
289
- }
290
- `;
291
- }
292
- //#endregion
293
- //#region src/production.ts
294
- const logger = getLogger("build");
295
- function getOutDirs(cwd, outputDir) {
296
- const outputRoot = resolve(cwd, outputDir);
297
- return {
298
- root: outputRoot,
299
- public: join(outputRoot, "public"),
300
- server: join(outputRoot, "server"),
301
- assets: join(outputRoot, "public", "assets"),
302
- virtual: join(cwd, ".ubean", "virtual")
303
- };
304
- }
305
- function toVitePath(p) {
306
- return p.replace(/\\/g, "/");
307
- }
308
- async function generateVirtualModulesToDisk(cwd, config, scanResult, virtualDir, preset, contentSnapshot) {
309
- const mode = config.mode;
310
- const ssrEnabled = mode === "fullstack" && config.ssr.enabled || mode === "ssg";
311
- const hasPages = mode !== "backend";
312
- const hasServer = mode !== "spa";
313
- await mkdir(virtualDir, { recursive: true });
314
- const registry = useVirtualRegistry();
315
- registry.clear();
316
- const srcDirAbs = resolve(cwd, config.srcDir);
317
- const viteSrcDir = toVitePath(relative(cwd, srcDirAbs));
318
- const viteSrcPrefix = viteSrcDir ? `/${viteSrcDir}` : "";
319
- registry.register(createRoutingVirtualModule(scanResult.apiRoutes.map((r) => ({
320
- method: r.method?.toUpperCase() || "ALL",
321
- path: r.route,
322
- id: `${r.method}:${r.route}`,
323
- filePath: r.fullPath
324
- })), scanResult.middlewares.map((m) => ({
325
- path: "/**",
326
- filePath: m.fullPath,
327
- order: m.order,
328
- global: m.global
329
- })), cwd));
330
- if (hasPages) {
331
- registry.register(createPagesVirtualModule(scanResult.pages.map((p) => ({
332
- name: p.name,
333
- path: p.route,
334
- filePath: p.fullPath,
335
- layout: p.layout,
336
- reuseTarget: p.reuseTarget
337
- })), scanResult.layouts.map((l) => ({
338
- name: l.name,
339
- filePath: l.fullPath,
340
- isDefault: l.isDefault
341
- })), cwd));
342
- registry.register(createMetaVirtualModule());
343
- registry.register(createVuePagesVirtualModule(scanResult.pages, scanResult.layouts, scanResult.notFoundPage, scanResult.loadingPage, scanResult.errorPage, localeVueParamFromI18n(config.i18n)));
344
- registry.register(createVueAppEntryVirtualModule(scanResult.appEntry));
345
- registry.register(createClientEntryVirtualModule());
346
- }
347
- registry.register(createAppVirtualModule(scanResult.apiRoutes, scanResult.middlewares, hasPages ? scanResult.pages : [], viteSrcPrefix || "/"));
348
- registry.register(createLocalesVirtualModule(scanResult.locales, scanResult.defaultLocale, viteSrcPrefix || "/", serializeI18nConfig(config.i18n)));
349
- if (hasServer) registry.register(createServerEntryVirtualModule(scanResult.serverEntry));
350
- async function writeModule(id, filename, header = "// Auto-generated by ubean\n") {
351
- const mod = registry.getModules().find((m) => m.id === id);
352
- const content = mod ? await mod.load() : "";
353
- await writeFile(join(virtualDir, filename), header + content, "utf-8");
354
- }
355
- await writeModule("ubean:routes", "routes.mjs");
356
- await writeModule("ubean:app-config", "app-config.mjs");
357
- await writeModule("ubean:locales", "locales.mjs");
358
- if (hasPages) {
359
- await writeModule("ubean:pages", "pages.ts");
360
- await writeModule("ubean:meta", "meta.mjs");
361
- await writeModule("virtual:ubean-pages", "vue-pages.ts", "");
362
- await writeModule("virtual:ubean-app", "vue-app.ts", "");
363
- await writeModule("virtual:ubean-client-entry", "client-entry.mjs", "");
364
- }
365
- if (hasServer) await writeModule("virtual:ubean-server", "server-entry.ts", "");
366
- await writeFile(join(virtualDir, "islands-registry.ts"), "export const islands = {};\n", "utf-8");
367
- const pagesGlob = JSON.stringify(`${viteSrcPrefix}/pages/**/*.{vue,ts,tsx,js,jsx,md,mdx}`);
368
- const routesGlob = JSON.stringify(`${viteSrcPrefix}/routes/**/*.{ts,js,mjs}`);
369
- const middlewareGlob = JSON.stringify(`${viteSrcPrefix}/middleware/**/*.{ts,js,mjs}`);
370
- const layoutsGlob = JSON.stringify(`${viteSrcPrefix}/layouts/**/*.{vue,ts}`);
371
- const cronsDirs = (Array.isArray(config.dir.crons) ? config.dir.crons : [config.dir.crons || "crons"]).map((d) => d.replace(/\\/g, "/"));
372
- const cronsGlob = JSON.stringify(cronsDirs.length === 1 ? `${viteSrcPrefix}/${cronsDirs[0]}/**/*.{ts,js,mjs}` : `${viteSrcPrefix}/{${cronsDirs.join(",")}}/**/*.{ts,js,mjs}`);
373
- const seoGlob = JSON.stringify(`${viteSrcPrefix}/{sitemap,robots,manifest,opengraph-image,icon,apple-icon}.{ts,js,mjs,mts,cjs}`);
374
- const enableInProcessCron = !isEphemeralCachePreset(preset.name || "standard");
375
- const enableIpx = Boolean(config.image);
376
- const outputDir = config.build.outputDir || "dist";
377
- const publicDir = toVitePath(resolve(cwd, outputDir, "public"));
378
- const routesJson = JSON.stringify(scanResult.apiRoutes.map((r) => ({
379
- relativePath: r.relativePath,
380
- route: r.route,
381
- method: r.method,
382
- fileMeta: r.fileMeta
383
- })));
384
- const middlewareJson = JSON.stringify(scanResult.middlewares.map((m) => ({
385
- relativePath: m.relativePath,
386
- order: m.order,
387
- global: m.global
388
- })));
389
- const pagesJson = JSON.stringify(scanResult.pages.map((p) => ({
390
- relativePath: p.relativePath,
391
- name: p.name,
392
- route: p.route,
393
- layout: p.layout,
394
- reuseTarget: p.reuseTarget,
395
- isReuse: p.isReuse,
396
- pageMeta: p.pageMeta
397
- })));
398
- const layoutsJson = JSON.stringify(scanResult.layouts.map((l) => ({
399
- name: l.name,
400
- relativePath: l.relativePath,
401
- isDefault: l.isDefault
402
- })));
403
- const notFoundPageJson = JSON.stringify(scanResult.notFoundPage ? {
404
- relativePath: scanResult.notFoundPage.relativePath,
405
- name: scanResult.notFoundPage.name,
406
- route: scanResult.notFoundPage.route,
407
- layout: scanResult.notFoundPage.layout,
408
- pageMeta: scanResult.notFoundPage.pageMeta
409
- } : null);
410
- const colorModeScript = config.colorMode !== false ? getColorModeScript(resolveColorModeConfig(config.colorMode)) : "";
411
- const rendererImport = ssrEnabled ? `import { createVueRenderer } from 'ubean/ssr';` : "";
412
- const prodLocaleVueParam = localeVueParamFromI18n(config.i18n) || "";
413
- const rendererSetup = buildRendererSetup({
414
- ssrEnabled,
415
- mode,
416
- localeVueParam: prodLocaleVueParam
417
- });
418
- const assetTagsSetup = buildAssetTagsSetup(config.favicon);
419
- const productionCache = resolveProductionCacheStore(preset.name || "standard", config.cache);
420
- const contentEntries = Object.entries(contentSnapshot ?? {}).filter(([, docs]) => Array.isArray(docs));
421
- const contentBootstrap = contentEntries.length > 0 ? `
422
- import { configureContentRuntime, registerContent } from '@ubean/content';
423
- configureContentRuntime();
424
- ${contentEntries.map(([name, docs]) => `registerContent(${JSON.stringify(name)}, ${JSON.stringify(docs)});`).join("\n")}
425
- ` : "";
426
- const ipxImport = enableIpx ? `import { createIpxHonoHandler } from '@ubean/image';
427
- ` : "";
428
- const cronImport = enableInProcessCron ? `import { startCronScheduler } from '@ubean/server/cron';
429
- ` : "";
430
- if (hasServer) {
431
- if (mode === "ssg") {
432
- const staticEntry = buildStaticSsgEntry({
433
- pagesGlob,
434
- layoutsGlob,
435
- srcPrefix: JSON.stringify(viteSrcPrefix),
436
- pagesJson,
437
- layoutsJson,
438
- notFoundPageJson,
439
- localeVueParam: prodLocaleVueParam,
440
- contentBootstrap,
441
- colorModeScript,
442
- favicon: config.favicon
443
- });
444
- await writeFile(join(virtualDir, "server-entry.mjs"), staticEntry, "utf-8");
445
- return virtualDir;
446
- }
447
- const serverEntry = `// Auto-generated server entry
448
- import { createUbeanApp, applyServerConfig } from 'ubean/server';
449
- import { toVueRouterLocalePath } from '@ubean/i18n';
450
- import 'ubean:locales';
451
- ${rendererImport}
452
- import { resolveAppConfig as _resolveAppConfig } from 'virtual:ubean-app';
453
- import { resolveServerConfig as _resolveServerConfig } from 'virtual:ubean-server';
454
- import { readFileSync } from 'node:fs';
455
- import { join, dirname } from 'node:path';
456
- import { fileURLToPath } from 'node:url';
457
- ${contentBootstrap}${ipxImport}${cronImport}
458
- export { createUbeanApp };
459
-
460
- const routeModules = import.meta.glob(${routesGlob}, { eager: false });
461
- const middlewareModules = import.meta.glob(${middlewareGlob}, { eager: false });
462
- const pageModules = import.meta.glob(${pagesGlob}, { eager: false });
463
- const layoutModules = import.meta.glob(${layoutsGlob}, { eager: false });
464
- const cronModules = import.meta.glob(${cronsGlob}, { eager: true });
465
- const seoConventionModules = import.meta.glob(${seoGlob}, { eager: true });
466
-
467
- const _srcPrefix = ${JSON.stringify(viteSrcPrefix)};
468
- function normalizeKey(p) {
469
- const prefixes = ['/routes/', '/middleware/', '/pages/', '/layouts/'];
470
- for (const prefix of prefixes) {
471
- const fullPrefix = _srcPrefix + prefix;
472
- if (p.includes(fullPrefix)) {
473
- const idx = p.indexOf(fullPrefix);
474
- return p.slice(idx + fullPrefix.length);
475
- }
476
- }
477
- return p;
478
- }
479
-
480
- export const routeLoaders = {};
481
- for (const [key, loader] of Object.entries(routeModules)) {
482
- routeLoaders[normalizeKey(key)] = loader;
483
- }
484
-
485
- export const middlewareLoaders = {};
486
- for (const [key, loader] of Object.entries(middlewareModules)) {
487
- middlewareLoaders[normalizeKey(key)] = loader;
488
- }
489
-
490
- export const pageLoaders = {};
491
- for (const [key, loader] of Object.entries(pageModules)) {
492
- pageLoaders[normalizeKey(key)] = loader;
493
- }
494
-
495
- const layoutLoaders = {};
496
- for (const [key, loader] of Object.entries(layoutModules)) {
497
- layoutLoaders[normalizeKey(key)] = loader;
498
- }
499
-
500
- const _routes = ${routesJson};
501
- const _middleware = ${middlewareJson};
502
- const _pages = ${pagesJson};
503
- const _layouts = ${layoutsJson};
504
- const _notFoundPage = ${notFoundPageJson};
505
- ${rendererSetup}
506
- ${assetTagsSetup}
507
-
508
- export async function createApp(options = {}) {
509
- const app = createUbeanApp({
510
- rootDir: ${JSON.stringify(cwd)},
511
- routes: _routes,
512
- middleware: _middleware,
513
- pages: _pages,
514
- layouts: _layouts,
515
- routeLoaders,
516
- middlewareLoaders,
517
- pageLoaders,
518
- pageRenderer: _pageRenderer,
519
- pageAssetTags: _assetTags,
520
- publicDir: ${JSON.stringify(publicDir)},
521
- i18nConfig: ${JSON.stringify(config.i18n)},
522
- ssrExclude: ${JSON.stringify(config.ssr?.exclude ?? [])},
523
- streaming: ${JSON.stringify(config.ssr?.streaming ?? false)},
524
- notFoundPage: _notFoundPage || undefined,
525
- colorModeScript: ${JSON.stringify(colorModeScript)},
526
- routeRules: ${JSON.stringify(config.routeRules || {})},
527
- csrf: ${JSON.stringify(config.security === false ? false : config.security?.csrf ?? true)},
528
- securityHeaders: ${JSON.stringify(config.security === false ? false : config.security?.headers ?? true)},
529
- dataCache: ${JSON.stringify(config.dataCache ?? true)},
530
- cache: ${JSON.stringify(productionCache)},
531
- seoConventions: { srcDir: ${JSON.stringify(srcDirAbs)} },
532
- seoConventionModules,
533
- ipxHandler: ${enableIpx ? `createIpxHonoHandler({ rootDir: ${JSON.stringify(publicDir)}, staticDir: '.' })` : "undefined"},
534
- ...options
535
- });
536
-
537
- // Apply user's defineServer config (plugins, hooks, onAppCreate) before init
538
- const _serverConfig = _resolveServerConfig('prod');
539
- await applyServerConfig(app, _serverConfig);
540
-
541
- await app.init();
542
-
543
- ${enableInProcessCron ? `if (Object.keys(cronModules).length > 0) {
544
- startCronScheduler();
545
- }` : ""}
546
-
547
- // Call onServerReady after init completes
548
- if (_serverConfig.onServerReady) {
549
- await _serverConfig.onServerReady(app);
550
- }
551
-
552
- return app;
553
- }
554
-
555
- export default async function createFetchHandler() {
556
- const app = await createApp();
557
- return app.fetch;
558
- }
559
-
560
- export const handler = async (req, ctx) => {
561
- const app = await createApp();
562
- return app.fetch(req, ctx);
563
- };
564
- `;
565
- await writeFile(join(virtualDir, "server-entry.mjs"), serverEntry, "utf-8");
566
- }
567
- return virtualDir;
568
- }
569
- function getPresetBuildConfig(preset) {
570
- switch (preset.name) {
571
- case "node":
572
- case "bun":
573
- case "deno": return {
574
- format: "esm",
575
- target: "node18",
576
- entryType: "node",
577
- external: [
578
- "hono",
579
- "vue",
580
- "vue/server-renderer",
581
- /^ubean(\/.*)?$/,
582
- /^node:/
583
- ]
584
- };
585
- case "cloudflare": return {
586
- format: "esm",
587
- target: "es2022",
588
- entryType: "worker",
589
- external: [
590
- "hono",
591
- "vue",
592
- "vue/server-renderer",
593
- /^ubean(\/.*)?$/
594
- ]
595
- };
596
- default: return {
597
- format: "esm",
598
- target: "es2022",
599
- entryType: "fetch",
600
- external: [
601
- "hono",
602
- "vue",
603
- "vue/server-renderer",
604
- /^ubean(\/.*)?$/
605
- ]
606
- };
607
- }
608
- }
609
- function generateNodeServerEntry() {
610
- return `// Auto-generated ubean Node.js server entry
611
- import { createServer } from 'node:http';
612
- import createFetchHandler from './entry.mjs';
613
-
614
- const port = Number(process.env.PORT || 9527);
615
- const host = process.env.HOST || '0.0.0.0';
616
-
617
- async function main() {
618
- const fetch = await createFetchHandler();
619
-
620
- const server = createServer(async (req, res) => {
621
- try {
622
- const protocol = req.socket?.encrypted ? 'https' : 'http';
623
- const url = \`\${protocol}://\${req.headers.host || host}\${req.url || '/'}\`;
624
- const headers = new Headers();
625
- for (const [key, value] of Object.entries(req.headers)) {
626
- if (value) {
627
- if (Array.isArray(value)) {
628
- for (const v of value) headers.append(key, v);
629
- } else {
630
- headers.set(key, value);
631
- }
632
- }
633
- }
634
- const method = req.method || 'GET';
635
- const body = method === 'GET' || method === 'HEAD' ? undefined : req;
636
- const webReq = new Request(url, { method, headers, body, duplex: 'half' });
637
- const webRes = await fetch(webReq);
638
-
639
- res.statusCode = webRes.status;
640
- res.statusMessage = webRes.statusText;
641
- webRes.headers.forEach((value, key) => res.setHeader(key, value));
642
-
643
- if (webRes.body) {
644
- const reader = webRes.body.getReader();
645
- try {
646
- while (true) {
647
- const { done, value: chunk } = await reader.read();
648
- if (done) break;
649
- res.write(chunk);
650
- }
651
- } finally {
652
- reader.releaseLock();
653
- }
654
- }
655
- res.end();
656
- } catch (err) {
657
- if (!res.headersSent) {
658
- res.statusCode = 500;
659
- res.setHeader('Content-Type', 'text/plain');
660
- }
661
- res.end(err instanceof Error ? err.message : String(err));
662
- }
663
- });
664
-
665
- server.listen(port, host, () => {
666
- console.log(\`ubean server listening on http://\${host}:\${port}\`);
667
- });
668
-
669
- const shutdown = () => {
670
- server.close(() => process.exit(0));
671
- };
672
- process.on('SIGINT', shutdown);
673
- process.on('SIGTERM', shutdown);
674
- }
675
-
676
- main();
677
- `.trim();
678
- }
679
- function generateStandardHandlerEntry() {
680
- return `// Auto-generated ubean standard fetch handler
681
- import createFetchHandler from './entry.mjs';
682
-
683
- const handlerPromise = createFetchHandler();
684
- export default async function fetch(req, ctx) {
685
- const handler = await handlerPromise;
686
- return handler(req, ctx);
687
- }
688
- `.trim();
689
- }
690
- function generateCloudflareWorkerEntry() {
691
- return `// Auto-generated ubean Cloudflare Worker entry
692
- import createFetchHandler from './entry.mjs';
693
-
694
- const handlerPromise = createFetchHandler();
695
- export default {
696
- async fetch(req, env, ctx) {
697
- const handler = await handlerPromise;
698
- return handler(req, { ...ctx, env });
699
- }
700
- };
701
- `.trim();
702
- }
703
- async function buildProduction(options) {
704
- const { cwd, config, preset, scanResult, minify = true, sourcemap = false, contentSnapshot } = options;
705
- const outDirs = getOutDirs(cwd, config.build.outputDir);
706
- const srcDir = resolve(cwd, config.srcDir);
707
- const mode = config.mode;
708
- const ssrEnabled = mode === "fullstack" && config.ssr.enabled || mode === "ssg";
709
- const hasPages = mode !== "backend";
710
- const hasServer = mode !== "spa";
711
- logger.info(`Cleaning output directory... (mode=${mode}${mode === "fullstack" ? `, ssr=${config.ssr.enabled}` : ""})`);
712
- if (existsSync(outDirs.root)) await rm(outDirs.root, {
713
- recursive: true,
714
- force: true
715
- });
716
- if (hasPages) await mkdir(outDirs.public, { recursive: true });
717
- if (hasServer) await mkdir(outDirs.server, { recursive: true });
718
- logger.info("Generating virtual modules...");
719
- await generateVirtualModulesToDisk(cwd, config, scanResult, outDirs.virtual, preset, contentSnapshot);
720
- const userViteConfig = findUserViteConfig(cwd);
721
- const builtinPlugins = [];
722
- if (hasPages) builtinPlugins.push(vue({
723
- include: VUE_PLUGIN_INCLUDE,
724
- template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith("ubean-") } }
725
- }));
726
- if (!userViteConfig) {
727
- builtinPlugins.push(ubeanPlugin({ config }));
728
- if (hasPages) builtinPlugins.push(...ubeanVite({ config }), ubeanIslandsPlugin());
729
- }
730
- const { plugins } = await resolveModules({
731
- cwd,
732
- config,
733
- builtinPlugins
734
- });
735
- const presetBuildConfig = getPresetBuildConfig(preset);
736
- const virtualDir = outDirs.virtual;
737
- const commonResolve = { alias: {
738
- "virtual:ubean-pages": join(virtualDir, "vue-pages.ts"),
739
- "virtual:ubean-app": join(virtualDir, "vue-app.ts"),
740
- "virtual:ubean-server": join(virtualDir, "server-entry.ts"),
741
- "virtual:ubean-client-entry": join(virtualDir, "client-entry.mjs"),
742
- "#ubean-pages": join(virtualDir, "vue-pages.ts"),
743
- "#ubean-app": join(virtualDir, "vue-app.ts"),
744
- "#ubean-server": join(virtualDir, "server-entry.ts"),
745
- "#ubean-client-entry": join(virtualDir, "client-entry.mjs"),
746
- "ubean:pages": join(virtualDir, "pages.ts"),
747
- "ubean:routes": join(virtualDir, "routes.mjs"),
748
- "ubean:app-config": join(virtualDir, "app-config.mjs"),
749
- "ubean:locales": join(virtualDir, "locales.mjs"),
750
- "ubean:meta": join(virtualDir, "meta.mjs")
751
- } };
752
- let clientManifest = {};
753
- if (hasPages) {
754
- logger.info("Building client bundle...");
755
- const clientEntryPath = join(srcDir, "entry.client.ts");
756
- const clientInput = existsSync(clientEntryPath) ? clientEntryPath : join(virtualDir, "client-entry.mjs");
757
- await writeFile(join(virtualDir, "index.html"), `<!DOCTYPE html>
758
- <html>
759
- <head>
760
- <meta charset="UTF-8" />
761
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
762
- <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
763
- <title>Ubean App</title>
764
- </head>
765
- <body>
766
- <div id="app"></div>
767
- <script type="module" src="virtual:ubean-client-entry"><\/script>
768
- </body>
769
- </html>`, "utf-8");
770
- await build({
771
- root: cwd,
772
- configFile: userViteConfig ?? false,
773
- mode: "production",
774
- build: {
775
- outDir: outDirs.public,
776
- assetsDir: "assets",
777
- minify: minify ? "oxc" : false,
778
- sourcemap,
779
- manifest: true,
780
- ssrManifest: true,
781
- rollupOptions: {
782
- input: { app: clientInput },
783
- output: {
784
- entryFileNames: "assets/[name]-[hash].js",
785
- chunkFileNames: "assets/chunks/[name]-[hash].js",
786
- assetFileNames: "assets/[name]-[hash].[ext]"
787
- }
788
- },
789
- emptyOutDir: false
790
- },
791
- plugins: [...plugins],
792
- resolve: commonResolve,
793
- optimizeDeps: { exclude: ssrSingletonProdOptimizeExclude([
794
- "virtual:ubean-pages",
795
- "virtual:ubean-app",
796
- "virtual:ubean-server",
797
- "virtual:ubean-client-entry",
798
- "#ubean-pages",
799
- "#ubean-app",
800
- "#ubean-server",
801
- "#ubean-client-entry"
802
- ]) }
803
- });
804
- const manifestPath = join(outDirs.public, ".vite", "manifest.json");
805
- if (existsSync(manifestPath)) try {
806
- clientManifest = JSON.parse(await readFile(manifestPath, "utf-8"));
807
- } catch {}
808
- const publicDir = join(cwd, "public");
809
- if (existsSync(publicDir)) {
810
- logger.info("Copying public assets...");
811
- await cp(publicDir, outDirs.public, { recursive: true });
812
- }
813
- }
814
- let serverEntry = "";
815
- if (hasServer) {
816
- logger.info(ssrEnabled ? "Building SSR bundle..." : "Building server bundle (SSR disabled)...");
817
- const serverEntryPath = join(virtualDir, "server-entry.mjs");
818
- await build({
819
- root: cwd,
820
- configFile: userViteConfig ?? false,
821
- mode: "production",
822
- ssr: {
823
- target: presetBuildConfig.target === "node18" ? "node" : "webworker",
824
- ...ssrSingletonProdSsr()
825
- },
826
- build: {
827
- outDir: outDirs.server,
828
- ssr: true,
829
- minify: false,
830
- sourcemap,
831
- rollupOptions: {
832
- input: serverEntryPath,
833
- external: presetBuildConfig.external.filter((e) => !(e instanceof RegExp && e.source.startsWith("^ubean"))),
834
- output: {
835
- format: presetBuildConfig.format,
836
- entryFileNames: "entry.mjs",
837
- chunkFileNames: "chunks/[name]-[hash].mjs",
838
- inlineDynamicImports: presetBuildConfig.entryType === "worker"
839
- }
840
- },
841
- emptyOutDir: false
842
- },
843
- plugins: [...plugins, {
844
- name: "ubean:islands-ssr-stub",
845
- enforce: "pre",
846
- resolveId(id) {
847
- if (id === "virtual:ubean-islands-registry") return "\0virtual:ubean-islands-registry";
848
- },
849
- load(id) {
850
- if (id === "\0virtual:ubean-islands-registry") return "export const islands = {};";
851
- }
852
- }],
853
- resolve: commonResolve
854
- });
855
- serverEntry = "entry.mjs";
856
- if (mode === "ssg") logger.info("SSG mode: skipping preset server wrapper (static output only)");
857
- else if (presetBuildConfig.entryType === "node") {
858
- await writeFile(join(outDirs.server, "server.mjs"), generateNodeServerEntry(), "utf-8");
859
- serverEntry = "server.mjs";
860
- await writeFile(join(outDirs.server, "package.json"), JSON.stringify({
861
- type: "module",
862
- private: true,
863
- main: "./server.mjs",
864
- dependencies: { hono: "^4.0.0" }
865
- }, null, 2), "utf-8");
866
- } else if (presetBuildConfig.entryType === "worker") {
867
- await writeFile(join(outDirs.server, "worker.mjs"), generateCloudflareWorkerEntry(), "utf-8");
868
- serverEntry = "worker.mjs";
869
- const wranglerToml = `
870
- name = "ubean-app"
871
- main = "./server/worker.mjs"
872
- compatibility_date = "2024-01-01"
873
- assets = { directory = "./public" }
874
- `.trim();
875
- await writeFile(join(outDirs.root, "wrangler.toml"), wranglerToml, "utf-8");
876
- } else {
877
- await writeFile(join(outDirs.server, "handler.mjs"), generateStandardHandlerEntry(), "utf-8");
878
- serverEntry = "handler.mjs";
879
- }
880
- }
881
- const assets = [];
882
- for (const [key, entry] of Object.entries(clientManifest)) assets.push({
883
- file: entry.file,
884
- src: key,
885
- isEntry: entry.isEntry,
886
- css: entry.css
887
- });
888
- const manifest = {
889
- assets,
890
- entry: serverEntry,
891
- clientDir: hasPages ? relative(cwd, outDirs.public) : "",
892
- serverDir: hasServer ? relative(cwd, outDirs.server) : "",
893
- preset: preset.name || "standard"
894
- };
895
- await writeFile(join(outDirs.root, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
896
- logger.info(`Build complete! Output: ${outDirs.root}`);
897
- if (hasPages) logger.info(` Client assets: ${outDirs.public}`);
898
- if (hasServer) {
899
- logger.info(` Server bundle: ${outDirs.server}`);
900
- logger.info(` Entry: ${serverEntry}`);
901
- }
902
- return manifest;
903
- }
904
- //#endregion
905
- export { buildProduction };
1
+ import { C as getPresetBuildConfig, S as generateVirtualModulesToDisk, T as buildWithEnvironments, b as generateNodeServerEntry, v as createIslandsSsrStubPlugin, w as serializePagesForEntry, x as generateStandardHandlerEntry, y as generateCloudflareWorkerEntry } from "./vite-VaaeRB17.js";
2
+ export { buildWithEnvironments, createIslandsSsrStubPlugin, generateCloudflareWorkerEntry, generateNodeServerEntry, generateStandardHandlerEntry, generateVirtualModulesToDisk, getPresetBuildConfig, serializePagesForEntry };