@ubean/build 0.2.2 → 0.3.1
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/actions-plugin.d.ts +62 -0
- package/dist/actions-plugin.js +385 -0
- package/dist/codegen/index.d.ts +175 -0
- package/dist/codegen/index.js +2 -0
- package/dist/codegen-CYk6X-Hn.js +691 -0
- package/dist/index.d.ts +95 -7
- package/dist/index.js +4 -3
- package/dist/prerender.d.ts +129 -0
- package/dist/prerender.js +308 -0
- package/dist/production.d.ts +2 -0
- package/dist/production.js +51 -21
- package/dist/ssr-singleton-CEQi_H6-.js +113 -0
- package/dist/{virtual-modules-BDgTqx3t.js → virtual-modules-D5WBA3u7.js} +175 -68
- package/dist/virtual-registry-BF0jYPle.d.ts +30 -0
- package/dist/virtual-registry-BWkaQHXN.js +69 -0
- package/dist/vite-B3y8Qoan.js +226 -0
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +1 -210
- package/dist/vue-CeSUQZ1J.js +694 -0
- package/dist/vue.d.ts +29 -0
- package/dist/vue.js +2 -0
- package/package.json +43 -20
package/dist/production.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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-D5WBA3u7.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-B3y8Qoan.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-CeSUQZ1J.js";
|
|
4
6
|
import { join, relative, resolve } from "pathe";
|
|
5
|
-
import {
|
|
6
|
-
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
7
|
+
import { resolveModules } from "@ubean/config";
|
|
7
8
|
import { build } from "vite";
|
|
8
|
-
import vue from "@vitejs/plugin-vue";
|
|
9
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";
|
|
10
13
|
import { ubeanIslandsPlugin } from "@ubean/islands/vite";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
14
|
+
import { isEphemeralCachePreset, resolveProductionCacheStore } from "@ubean/preset";
|
|
15
|
+
import { getLogger } from "@ubean/shared/logger";
|
|
13
16
|
import { findUserViteConfig } from "@ubean/shared/node";
|
|
14
|
-
import { VUE_PLUGIN_INCLUDE, createClientEntryVirtualModule, createServerEntryVirtualModule, createVueAppEntryVirtualModule, createVuePagesVirtualModule, ubeanVite } from "@ubean/vite";
|
|
15
17
|
//#region src/production.ts
|
|
16
18
|
const logger = getLogger("build");
|
|
17
19
|
function getOutDirs(cwd, outputDir) {
|
|
@@ -27,7 +29,7 @@ function getOutDirs(cwd, outputDir) {
|
|
|
27
29
|
function toVitePath(p) {
|
|
28
30
|
return p.replace(/\\/g, "/");
|
|
29
31
|
}
|
|
30
|
-
async function generateVirtualModulesToDisk(cwd, config, scanResult, virtualDir) {
|
|
32
|
+
async function generateVirtualModulesToDisk(cwd, config, scanResult, virtualDir, preset, contentSnapshot) {
|
|
31
33
|
const mode = config.mode;
|
|
32
34
|
const ssrEnabled = mode === "fullstack" && config.ssr.enabled || mode === "ssg";
|
|
33
35
|
const hasPages = mode !== "backend";
|
|
@@ -62,12 +64,12 @@ async function generateVirtualModulesToDisk(cwd, config, scanResult, virtualDir)
|
|
|
62
64
|
isDefault: l.isDefault
|
|
63
65
|
})), cwd));
|
|
64
66
|
registry.register(createMetaVirtualModule());
|
|
65
|
-
registry.register(createVuePagesVirtualModule(scanResult.pages, scanResult.layouts, scanResult.notFoundPage, scanResult.loadingPage, scanResult.errorPage));
|
|
67
|
+
registry.register(createVuePagesVirtualModule(scanResult.pages, scanResult.layouts, scanResult.notFoundPage, scanResult.loadingPage, scanResult.errorPage, localeVueParamFromI18n(config.i18n)));
|
|
66
68
|
registry.register(createVueAppEntryVirtualModule(scanResult.appEntry));
|
|
67
69
|
registry.register(createClientEntryVirtualModule());
|
|
68
70
|
}
|
|
69
71
|
registry.register(createAppVirtualModule(scanResult.apiRoutes, scanResult.middlewares, hasPages ? scanResult.pages : [], viteSrcPrefix || "/"));
|
|
70
|
-
registry.register(createLocalesVirtualModule(scanResult.locales, scanResult.defaultLocale, viteSrcPrefix || "/"));
|
|
72
|
+
registry.register(createLocalesVirtualModule(scanResult.locales, scanResult.defaultLocale, viteSrcPrefix || "/", serializeI18nConfig(config.i18n)));
|
|
71
73
|
if (hasServer) registry.register(createServerEntryVirtualModule(scanResult.serverEntry));
|
|
72
74
|
async function writeModule(id, filename, header = "// Auto-generated by ubean\n") {
|
|
73
75
|
const mod = registry.getModules().find((m) => m.id === id);
|
|
@@ -90,6 +92,11 @@ async function generateVirtualModulesToDisk(cwd, config, scanResult, virtualDir)
|
|
|
90
92
|
const routesGlob = JSON.stringify(`${viteSrcPrefix}/routes/**/*.{ts,js,mjs}`);
|
|
91
93
|
const middlewareGlob = JSON.stringify(`${viteSrcPrefix}/middleware/**/*.{ts,js,mjs}`);
|
|
92
94
|
const layoutsGlob = JSON.stringify(`${viteSrcPrefix}/layouts/**/*.{vue,ts}`);
|
|
95
|
+
const cronsDirs = (Array.isArray(config.dir.crons) ? config.dir.crons : [config.dir.crons || "crons"]).map((d) => d.replace(/\\/g, "/"));
|
|
96
|
+
const cronsGlob = JSON.stringify(cronsDirs.length === 1 ? `${viteSrcPrefix}/${cronsDirs[0]}/**/*.{ts,js,mjs}` : `${viteSrcPrefix}/{${cronsDirs.join(",")}}/**/*.{ts,js,mjs}`);
|
|
97
|
+
const seoGlob = JSON.stringify(`${viteSrcPrefix}/{sitemap,robots,manifest,opengraph-image,icon,apple-icon}.{ts,js,mjs,mts,cjs}`);
|
|
98
|
+
const enableInProcessCron = !isEphemeralCachePreset(preset.name || "standard");
|
|
99
|
+
const enableIpx = Boolean(config.image);
|
|
93
100
|
const outputDir = config.build.outputDir || "dist";
|
|
94
101
|
const publicDir = toVitePath(resolve(cwd, outputDir, "public"));
|
|
95
102
|
const routesJson = JSON.stringify(scanResult.apiRoutes.map((r) => ({
|
|
@@ -175,23 +182,36 @@ const _pageRenderer = createVueRenderer({
|
|
|
175
182
|
// --- SSR disabled (mode=${mode}, ssr=${config.ssr.enabled}) ---
|
|
176
183
|
// pageRenderer is null; page requests fall back to a client-only HTML shell.
|
|
177
184
|
const _pageRenderer = null;`;
|
|
185
|
+
const productionCache = resolveProductionCacheStore(preset.name || "standard", config.cache);
|
|
186
|
+
const contentEntries = Object.entries(contentSnapshot ?? {}).filter(([, docs]) => Array.isArray(docs));
|
|
187
|
+
const contentBootstrap = contentEntries.length > 0 ? `
|
|
188
|
+
import { configureContentRuntime, registerContent } from '@ubean/content';
|
|
189
|
+
configureContentRuntime();
|
|
190
|
+
${contentEntries.map(([name, docs]) => `registerContent(${JSON.stringify(name)}, ${JSON.stringify(docs)});`).join("\n")}
|
|
191
|
+
` : "";
|
|
192
|
+
const ipxImport = enableIpx ? `import { createIpxHonoHandler } from '@ubean/image';
|
|
193
|
+
` : "";
|
|
194
|
+
const cronImport = enableInProcessCron ? `import { startCronScheduler } from '@ubean/server/cron';
|
|
195
|
+
` : "";
|
|
178
196
|
if (hasServer) {
|
|
179
197
|
const serverEntry = `// Auto-generated server entry
|
|
180
198
|
import { createUbeanApp, applyServerConfig } from 'ubean/runtime/app';
|
|
199
|
+
import 'ubean:locales';
|
|
181
200
|
${rendererImport}
|
|
182
201
|
import { resolveAppConfig as _resolveAppConfig } from 'virtual:ubean-app';
|
|
183
202
|
import { resolveServerConfig as _resolveServerConfig } from 'virtual:ubean-server';
|
|
184
|
-
import { loadLocales } from 'ubean:locales';
|
|
185
203
|
import { readFileSync } from 'node:fs';
|
|
186
204
|
import { join, dirname } from 'node:path';
|
|
187
205
|
import { fileURLToPath } from 'node:url';
|
|
188
|
-
|
|
206
|
+
${contentBootstrap}${ipxImport}${cronImport}
|
|
189
207
|
export { createUbeanApp };
|
|
190
208
|
|
|
191
209
|
const routeModules = import.meta.glob(${routesGlob}, { eager: false });
|
|
192
210
|
const middlewareModules = import.meta.glob(${middlewareGlob}, { eager: false });
|
|
193
211
|
const pageModules = import.meta.glob(${pagesGlob}, { eager: false });
|
|
194
212
|
const layoutModules = import.meta.glob(${layoutsGlob}, { eager: false });
|
|
213
|
+
const cronModules = import.meta.glob(${cronsGlob}, { eager: true });
|
|
214
|
+
const seoConventionModules = import.meta.glob(${seoGlob}, { eager: true });
|
|
195
215
|
|
|
196
216
|
const _srcPrefix = ${JSON.stringify(viteSrcPrefix)};
|
|
197
217
|
function normalizeKey(p) {
|
|
@@ -267,6 +287,14 @@ export async function createApp(options = {}) {
|
|
|
267
287
|
streaming: ${JSON.stringify(config.ssr?.streaming ?? false)},
|
|
268
288
|
notFoundPage: _notFoundPage || undefined,
|
|
269
289
|
colorModeScript: ${JSON.stringify(colorModeScript)},
|
|
290
|
+
routeRules: ${JSON.stringify(config.routeRules || {})},
|
|
291
|
+
csrf: ${JSON.stringify(config.security === false ? false : config.security?.csrf ?? true)},
|
|
292
|
+
securityHeaders: ${JSON.stringify(config.security === false ? false : config.security?.headers ?? true)},
|
|
293
|
+
dataCache: ${JSON.stringify(config.dataCache ?? true)},
|
|
294
|
+
cache: ${JSON.stringify(productionCache)},
|
|
295
|
+
seoConventions: { srcDir: ${JSON.stringify(srcDirAbs)} },
|
|
296
|
+
seoConventionModules,
|
|
297
|
+
ipxHandler: ${enableIpx ? `createIpxHonoHandler({ rootDir: ${JSON.stringify(publicDir)}, staticDir: '.' })` : "undefined"},
|
|
270
298
|
...options
|
|
271
299
|
});
|
|
272
300
|
|
|
@@ -274,9 +302,12 @@ export async function createApp(options = {}) {
|
|
|
274
302
|
const _serverConfig = _resolveServerConfig('prod');
|
|
275
303
|
await applyServerConfig(app, _serverConfig);
|
|
276
304
|
|
|
277
|
-
await loadLocales();
|
|
278
305
|
await app.init();
|
|
279
306
|
|
|
307
|
+
${enableInProcessCron ? `if (Object.keys(cronModules).length > 0) {
|
|
308
|
+
startCronScheduler();
|
|
309
|
+
}` : ""}
|
|
310
|
+
|
|
280
311
|
// Call onServerReady after init completes
|
|
281
312
|
if (_serverConfig.onServerReady) {
|
|
282
313
|
await _serverConfig.onServerReady(app);
|
|
@@ -434,7 +465,7 @@ export default {
|
|
|
434
465
|
`.trim();
|
|
435
466
|
}
|
|
436
467
|
async function buildProduction(options) {
|
|
437
|
-
const { cwd, config, preset, scanResult, minify = true, sourcemap = false } = options;
|
|
468
|
+
const { cwd, config, preset, scanResult, minify = true, sourcemap = false, contentSnapshot } = options;
|
|
438
469
|
const outDirs = getOutDirs(cwd, config.build.outputDir);
|
|
439
470
|
const srcDir = resolve(cwd, config.srcDir);
|
|
440
471
|
const mode = config.mode;
|
|
@@ -449,7 +480,7 @@ async function buildProduction(options) {
|
|
|
449
480
|
if (hasPages) await mkdir(outDirs.public, { recursive: true });
|
|
450
481
|
if (hasServer) await mkdir(outDirs.server, { recursive: true });
|
|
451
482
|
logger.info("Generating virtual modules...");
|
|
452
|
-
await generateVirtualModulesToDisk(cwd, config, scanResult, outDirs.virtual);
|
|
483
|
+
await generateVirtualModulesToDisk(cwd, config, scanResult, outDirs.virtual, preset, contentSnapshot);
|
|
453
484
|
const userViteConfig = findUserViteConfig(cwd);
|
|
454
485
|
const builtinPlugins = [];
|
|
455
486
|
if (hasPages) builtinPlugins.push(vue({
|
|
@@ -523,8 +554,7 @@ async function buildProduction(options) {
|
|
|
523
554
|
},
|
|
524
555
|
plugins: [...plugins],
|
|
525
556
|
resolve: commonResolve,
|
|
526
|
-
optimizeDeps: { exclude: [
|
|
527
|
-
"ubean",
|
|
557
|
+
optimizeDeps: { exclude: ssrSingletonProdOptimizeExclude([
|
|
528
558
|
"virtual:ubean-pages",
|
|
529
559
|
"virtual:ubean-app",
|
|
530
560
|
"virtual:ubean-server",
|
|
@@ -533,7 +563,7 @@ async function buildProduction(options) {
|
|
|
533
563
|
"#ubean-app",
|
|
534
564
|
"#ubean-server",
|
|
535
565
|
"#ubean-client-entry"
|
|
536
|
-
] }
|
|
566
|
+
]) }
|
|
537
567
|
});
|
|
538
568
|
const manifestPath = join(outDirs.public, ".vite", "manifest.json");
|
|
539
569
|
if (existsSync(manifestPath)) try {
|
|
@@ -555,7 +585,7 @@ async function buildProduction(options) {
|
|
|
555
585
|
mode: "production",
|
|
556
586
|
ssr: {
|
|
557
587
|
target: presetBuildConfig.target === "node18" ? "node" : "webworker",
|
|
558
|
-
|
|
588
|
+
...ssrSingletonProdSsr()
|
|
559
589
|
},
|
|
560
590
|
build: {
|
|
561
591
|
outDir: outDirs.server,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
//#region src/registry.ts
|
|
2
|
+
const REGISTRY_KEY = "__ubean_module_registry__";
|
|
3
|
+
function getRegistry() {
|
|
4
|
+
const g = globalThis;
|
|
5
|
+
if (!g[REGISTRY_KEY]) g[REGISTRY_KEY] = {
|
|
6
|
+
componentResolvers: [],
|
|
7
|
+
cssImports: []
|
|
8
|
+
};
|
|
9
|
+
return g[REGISTRY_KEY];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Register a component resolver (e.g. `UiResolver()` from `@soybeanjs/ui/resolver`).
|
|
13
|
+
* The resolver will be merged into `unplugin-vue-components`'s `resolvers` array.
|
|
14
|
+
*/
|
|
15
|
+
function registerComponentResolver(resolver) {
|
|
16
|
+
getRegistry().componentResolvers.push(resolver);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get all registered component resolvers.
|
|
20
|
+
* Called by `ubeanVite` when constructing the `Components()` plugin.
|
|
21
|
+
*/
|
|
22
|
+
function getComponentResolvers() {
|
|
23
|
+
return [...getRegistry().componentResolvers];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Register a CSS import path to be injected into the client entry.
|
|
27
|
+
* e.g. `registerCssImport('@soybeanjs/ui/styles.css')` makes the client
|
|
28
|
+
* entry module prepend `import '@soybeanjs/ui/styles.css'`.
|
|
29
|
+
*/
|
|
30
|
+
function registerCssImport(cssPath) {
|
|
31
|
+
const reg = getRegistry();
|
|
32
|
+
if (!reg.cssImports.includes(cssPath)) reg.cssImports.push(cssPath);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get all registered CSS import paths.
|
|
36
|
+
* Called by `createClientEntryVirtualModule` to prepend CSS imports.
|
|
37
|
+
*/
|
|
38
|
+
function getCssImports() {
|
|
39
|
+
return [...getRegistry().cssImports];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Reset the registry (for testing).
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
function resetModuleRegistry() {
|
|
46
|
+
const reg = getRegistry();
|
|
47
|
+
reg.componentResolvers.length = 0;
|
|
48
|
+
reg.cssImports.length = 0;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/ssr-singleton.ts
|
|
52
|
+
/**
|
|
53
|
+
* Single-copy runtime policy for Vite client / SSR graphs.
|
|
54
|
+
*
|
|
55
|
+
* `@ubean/i18n` (ALS + catalogs) and `@ubean/client` (locale runtime) must
|
|
56
|
+
* resolve to one module instance per process. Dev and production use the
|
|
57
|
+
* same package lists; only the SSR externalization differs:
|
|
58
|
+
*
|
|
59
|
+
* - Dev: Vite `ssrLoadModule` and the CLI-created app share Node's
|
|
60
|
+
* `@ubean/i18n` (external). `ubean` stays noExternal so virtual ids resolve.
|
|
61
|
+
* - Prod: `ubean` is bundled (`noExternal`). Do **not** also externalize
|
|
62
|
+
* `@ubean/i18n` — that would create a second copy beside the bundle.
|
|
63
|
+
*/
|
|
64
|
+
const SSR_SINGLETON_PACKAGES = [
|
|
65
|
+
"ubean",
|
|
66
|
+
"@ubean/client",
|
|
67
|
+
"@ubean/i18n",
|
|
68
|
+
"vue-i18n",
|
|
69
|
+
"@intlify/core",
|
|
70
|
+
"@intlify/core-base"
|
|
71
|
+
];
|
|
72
|
+
const SSR_SINGLETON_DEDUPE = [
|
|
73
|
+
"vue",
|
|
74
|
+
"vue-i18n",
|
|
75
|
+
"@ubean/client",
|
|
76
|
+
"@ubean/i18n"
|
|
77
|
+
];
|
|
78
|
+
const SSR_SINGLETON_OPTIMIZE_EXCLUDE = [
|
|
79
|
+
"ubean",
|
|
80
|
+
"@ubean/client",
|
|
81
|
+
"@ubean/i18n"
|
|
82
|
+
];
|
|
83
|
+
function ssrSingletonDevPolicy() {
|
|
84
|
+
return {
|
|
85
|
+
resolve: { dedupe: [...SSR_SINGLETON_DEDUPE] },
|
|
86
|
+
optimizeDeps: {
|
|
87
|
+
exclude: [...SSR_SINGLETON_OPTIMIZE_EXCLUDE],
|
|
88
|
+
include: ["vue-i18n"]
|
|
89
|
+
},
|
|
90
|
+
ssr: {
|
|
91
|
+
noExternal: [
|
|
92
|
+
"ubean",
|
|
93
|
+
"vue-i18n",
|
|
94
|
+
"@intlify/core",
|
|
95
|
+
"@intlify/core-base"
|
|
96
|
+
],
|
|
97
|
+
external: ["@ubean/i18n"]
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function ssrSingletonProdSsr() {
|
|
102
|
+
return { noExternal: [
|
|
103
|
+
"ubean",
|
|
104
|
+
"vue-i18n",
|
|
105
|
+
"@intlify/core",
|
|
106
|
+
"@intlify/core-base"
|
|
107
|
+
] };
|
|
108
|
+
}
|
|
109
|
+
function ssrSingletonProdOptimizeExclude(extra = []) {
|
|
110
|
+
return [...SSR_SINGLETON_OPTIMIZE_EXCLUDE, ...extra];
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
export { ssrSingletonProdOptimizeExclude as a, getCssImports as c, resetModuleRegistry as d, ssrSingletonDevPolicy as i, registerComponentResolver as l, SSR_SINGLETON_OPTIMIZE_EXCLUDE as n, ssrSingletonProdSsr as o, SSR_SINGLETON_PACKAGES as r, getComponentResolvers as s, SSR_SINGLETON_DEDUPE as t, registerCssImport as u };
|
|
@@ -1,5 +1,111 @@
|
|
|
1
|
-
import { defineVirtualModule } from "
|
|
1
|
+
import { n as defineVirtualModule } from "./virtual-registry-BWkaQHXN.js";
|
|
2
2
|
import { isAbsolute, relative } from "pathe";
|
|
3
|
+
//#region src/macros.ts
|
|
4
|
+
const MACRO_NAMES = ["definePage"];
|
|
5
|
+
function findBalancedCall(code, funcName, startSearch = 0) {
|
|
6
|
+
const pattern = new RegExp(`\\b${funcName}\\s*\\(`, "g");
|
|
7
|
+
pattern.lastIndex = startSearch;
|
|
8
|
+
const match = pattern.exec(code);
|
|
9
|
+
if (!match) return null;
|
|
10
|
+
const startIdx = match.index;
|
|
11
|
+
const parenStart = match.index + match[0].length;
|
|
12
|
+
let depth = 1;
|
|
13
|
+
let i = parenStart;
|
|
14
|
+
let inString = null;
|
|
15
|
+
let escaped = false;
|
|
16
|
+
let inTemplateExpr = 0;
|
|
17
|
+
while (i < code.length && depth > 0) {
|
|
18
|
+
const ch = code[i];
|
|
19
|
+
if (escaped) {
|
|
20
|
+
escaped = false;
|
|
21
|
+
i++;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (ch === "\\") {
|
|
25
|
+
escaped = true;
|
|
26
|
+
i++;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (inString) {
|
|
30
|
+
if (inString === "`" && ch === "$" && code[i + 1] === "{") {
|
|
31
|
+
inTemplateExpr++;
|
|
32
|
+
i += 2;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (inString === "`" && ch === "}" && inTemplateExpr > 0) {
|
|
36
|
+
inTemplateExpr--;
|
|
37
|
+
i++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (ch === inString) inString = null;
|
|
41
|
+
i++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (ch === "\"" || ch === "'" || ch === "`") {
|
|
45
|
+
inString = ch;
|
|
46
|
+
i++;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (ch === "/" && code[i + 1] === "/") {
|
|
50
|
+
while (i < code.length && code[i] !== "\n") i++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (ch === "/" && code[i + 1] === "*") {
|
|
54
|
+
i += 2;
|
|
55
|
+
while (i < code.length && !(code[i] === "*" && code[i + 1] === "/")) i++;
|
|
56
|
+
i += 2;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (ch === "(" || ch === "{" || ch === "[") depth++;
|
|
60
|
+
else if (ch === ")" || ch === "}" || ch === "]") {
|
|
61
|
+
depth--;
|
|
62
|
+
if (depth === 0 && ch === ")") return {
|
|
63
|
+
start: startIdx,
|
|
64
|
+
end: i + 1
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
function stripStatement(code, callStart, callEnd) {
|
|
72
|
+
let start = callStart;
|
|
73
|
+
let end = callEnd;
|
|
74
|
+
while (start > 0 && /[ \t]/.test(code[start - 1])) start--;
|
|
75
|
+
const exportDefaultMatch = code.slice(0, start).match(/export\s+default\s*$/);
|
|
76
|
+
if (exportDefaultMatch) {
|
|
77
|
+
start -= exportDefaultMatch[0].length;
|
|
78
|
+
while (start > 0 && /[ \t\n\r]/.test(code[start - 1])) start--;
|
|
79
|
+
}
|
|
80
|
+
if (code[end] === ";") end++;
|
|
81
|
+
while (end < code.length && /[ \t]/.test(code[end])) end++;
|
|
82
|
+
return code.slice(0, start) + code.slice(end);
|
|
83
|
+
}
|
|
84
|
+
function stripMacros(code, macros = MACRO_NAMES) {
|
|
85
|
+
let result = code;
|
|
86
|
+
for (const macro of macros) {
|
|
87
|
+
let offset = 0;
|
|
88
|
+
while (true) {
|
|
89
|
+
const found = findBalancedCall(result, macro, offset);
|
|
90
|
+
if (!found) break;
|
|
91
|
+
result = stripStatement(result, found.start, found.end);
|
|
92
|
+
offset = found.start;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
function transformMacros(code, id) {
|
|
98
|
+
if (!(id.includes("/src/pages/") || id.includes("/src/routes/") || id.includes("/src/middleware/"))) return null;
|
|
99
|
+
if (id.endsWith(".vue")) return transformVueMacros(code);
|
|
100
|
+
if (/\.(ts|js|mjs|mts|tsx|jsx)$/.test(id)) return stripMacros(code);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
function transformVueMacros(code) {
|
|
104
|
+
return code.replace(/<script([^>]*)>([\s\S]*?)<\/script>/g, (_match, attrs, content) => {
|
|
105
|
+
return `<script${attrs}>${stripMacros(content)}<\/script>`;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
3
109
|
//#region src/virtual-modules.ts
|
|
4
110
|
function toVitePath(p) {
|
|
5
111
|
return p.replace(/\\/g, "/");
|
|
@@ -94,18 +200,20 @@ export default { routeLoaders, middlewareLoaders, pageLoaders, apiRoutes, middle
|
|
|
94
200
|
`;
|
|
95
201
|
});
|
|
96
202
|
}
|
|
97
|
-
function createLocalesVirtualModule(_locales, defaultLocale, srcDir = "src") {
|
|
203
|
+
function createLocalesVirtualModule(_locales, defaultLocale, srcDir = "src", i18nConfigJson) {
|
|
98
204
|
return defineVirtualModule("ubean:locales", () => {
|
|
99
205
|
const defaultCode = defaultLocale == null ? "null" : JSON.stringify(defaultLocale);
|
|
100
206
|
const viteSrcDir = toVitePath(srcDir);
|
|
101
207
|
const prefix = viteSrcDir === "/" || viteSrcDir === "" ? "" : viteSrcDir;
|
|
208
|
+
const localesGlob = JSON.stringify(`${prefix}/locales/**/*.{json,json5,yaml,yml,js,mjs,cjs,ts,mts,cts}`);
|
|
209
|
+
const srcPrefix = JSON.stringify(`${prefix || ""}/locales/`);
|
|
102
210
|
return `
|
|
103
|
-
|
|
211
|
+
export const i18nConfig = ${i18nConfigJson ?? "null"};
|
|
104
212
|
|
|
105
|
-
const localeModules = import.meta.glob([${
|
|
213
|
+
const localeModules = import.meta.glob([${localesGlob}], { eager: false });
|
|
106
214
|
|
|
107
215
|
function parseLocalePath(path) {
|
|
108
|
-
const withoutPrefix = path.replace(${
|
|
216
|
+
const withoutPrefix = path.replace(${srcPrefix}, '');
|
|
109
217
|
const lastDot = withoutPrefix.lastIndexOf('.');
|
|
110
218
|
const withoutExt = lastDot > 0 ? withoutPrefix.slice(0, lastDot) : withoutPrefix;
|
|
111
219
|
const parts = withoutExt.split('/');
|
|
@@ -142,94 +250,93 @@ function setNestedValue(obj, path, value) {
|
|
|
142
250
|
current[path[path.length - 1]] = value;
|
|
143
251
|
}
|
|
144
252
|
|
|
145
|
-
|
|
253
|
+
const cache = new Map();
|
|
146
254
|
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
|
|
255
|
+
function codesFromConfig() {
|
|
256
|
+
if (!i18nConfig || !i18nConfig.locales) return [];
|
|
257
|
+
return i18nConfig.locales.map(l => typeof l === 'string' ? l : l.code);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export const localeCodes = codesFromConfig();
|
|
261
|
+
const defaultCode = ${defaultCode};
|
|
262
|
+
|
|
263
|
+
let i18nRuntime = null;
|
|
264
|
+
if (import.meta.env.SSR) {
|
|
265
|
+
i18nRuntime = await import('ubean/runtime/i18n');
|
|
266
|
+
if (i18nConfig && i18nConfig.fallbackLocale) {
|
|
267
|
+
i18nRuntime.setFallbackLocale(i18nConfig.fallbackLocale);
|
|
268
|
+
} else if (defaultCode) {
|
|
269
|
+
i18nRuntime.setFallbackLocale(defaultCode);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
150
272
|
|
|
151
|
-
|
|
273
|
+
async function collectMessages(code) {
|
|
274
|
+
const localeData = { messages: {}, options: { isDefault: false, name: undefined, dir: 'ltr', language: undefined } };
|
|
275
|
+
const configLoc = i18nConfig && i18nConfig.locales
|
|
276
|
+
? i18nConfig.locales.find(l => (typeof l === 'string' ? l : l.code) === code)
|
|
277
|
+
: undefined;
|
|
278
|
+
if (configLoc && typeof configLoc === 'object') {
|
|
279
|
+
localeData.options.name = configLoc.name;
|
|
280
|
+
localeData.options.dir = configLoc.dir || 'ltr';
|
|
281
|
+
localeData.options.language = configLoc.language;
|
|
282
|
+
localeData.options.isDefault = configLoc.code === (i18nConfig && i18nConfig.defaultLocale);
|
|
283
|
+
}
|
|
152
284
|
|
|
153
285
|
for (const [path, loader] of Object.entries(localeModules)) {
|
|
286
|
+
const parsed = parseLocalePath(path);
|
|
287
|
+
if (parsed.code !== code) continue;
|
|
154
288
|
try {
|
|
155
|
-
const { code, namespace, isDefault } = parseLocalePath(path);
|
|
156
289
|
const mod = await loader();
|
|
157
290
|
const data = mod.default || mod;
|
|
158
291
|
const hasMeta = typeof data.name === 'string' || data.dir === 'ltr' || data.dir === 'rtl' || typeof data.isDefault === 'boolean';
|
|
159
292
|
const isWrapper = hasMeta && typeof data.messages === 'object' && data.messages !== null;
|
|
160
293
|
const messages = isWrapper ? data.messages : data;
|
|
161
|
-
|
|
162
|
-
name
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
if (!localeData.has(code)) {
|
|
168
|
-
localeData.set(code, { messages: {}, options: { isDefault: options.isDefault || isDefault, name: options.name, dir: options.dir } });
|
|
294
|
+
if (isWrapper) {
|
|
295
|
+
if (data.name) localeData.options.name = data.name;
|
|
296
|
+
if (data.dir) localeData.options.dir = data.dir;
|
|
297
|
+
if (data.isDefault) localeData.options.isDefault = true;
|
|
169
298
|
}
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
if (options.isDefault) entry.options.isDefault = true;
|
|
174
|
-
|
|
175
|
-
if (namespace) {
|
|
176
|
-
setNestedValue(entry.messages, namespace.split('.'), messages);
|
|
299
|
+
if (parsed.isDefault) localeData.options.isDefault = true;
|
|
300
|
+
if (parsed.namespace) {
|
|
301
|
+
setNestedValue(localeData.messages, parsed.namespace.split('.'), messages);
|
|
177
302
|
} else {
|
|
178
|
-
Object.assign(
|
|
303
|
+
Object.assign(localeData.messages, messages);
|
|
179
304
|
}
|
|
180
305
|
} catch (e) {
|
|
181
306
|
console.warn('[ubean] Failed to load locale:', path, e);
|
|
182
307
|
}
|
|
183
308
|
}
|
|
309
|
+
return localeData;
|
|
310
|
+
}
|
|
184
311
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
isDefault: options.isDefault
|
|
192
|
-
});
|
|
312
|
+
export async function loadLocale(code) {
|
|
313
|
+
if (cache.has(code)) return cache.get(code);
|
|
314
|
+
const { messages, options } = await collectMessages(code);
|
|
315
|
+
if (i18nRuntime) {
|
|
316
|
+
i18nRuntime.setLocaleMessages(code, messages);
|
|
317
|
+
i18nRuntime.setLocaleMeta(code, options);
|
|
193
318
|
}
|
|
319
|
+
cache.set(code, messages);
|
|
320
|
+
return messages;
|
|
321
|
+
}
|
|
194
322
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
} else if (localeData.size > 0) {
|
|
199
|
-
const firstDefault = [...localeData.entries()].find(([, v]) => v.options.isDefault);
|
|
200
|
-
if (firstDefault) {
|
|
201
|
-
setLocale(firstDefault[0]);
|
|
202
|
-
} else {
|
|
203
|
-
setLocale([...localeData.keys()][0]);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
323
|
+
export async function loadLocales() {
|
|
324
|
+
const codes = localeCodes.length ? localeCodes : [...new Set(Object.keys(localeModules).map(p => parseLocalePath(p).code))];
|
|
325
|
+
await Promise.all(codes.map(loadLocale));
|
|
206
326
|
}
|
|
207
327
|
|
|
208
328
|
export async function reloadLocale(path) {
|
|
209
329
|
if (!path.includes('/locales/')) return;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const isWrapper = hasMeta && typeof data.messages === 'object' && data.messages !== null;
|
|
218
|
-
const messages = isWrapper ? data.messages : data;
|
|
219
|
-
const merged = {};
|
|
220
|
-
if (namespace) {
|
|
221
|
-
setNestedValue(merged, namespace.split('.'), messages);
|
|
222
|
-
} else {
|
|
223
|
-
Object.assign(merged, messages);
|
|
224
|
-
}
|
|
225
|
-
mergeLocale(code, merged);
|
|
226
|
-
}
|
|
227
|
-
} catch (e) {
|
|
228
|
-
console.warn('[ubean] Failed to reload locale:', path, e);
|
|
229
|
-
}
|
|
330
|
+
cache.clear();
|
|
331
|
+
const { code } = parseLocalePath(path);
|
|
332
|
+
await loadLocale(code);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (i18nRuntime) {
|
|
336
|
+
i18nRuntime.registerLocaleLoader(loadLocale);
|
|
230
337
|
}
|
|
231
338
|
|
|
232
|
-
export default { loadLocales, reloadLocale };
|
|
339
|
+
export default { loadLocale, loadLocales, reloadLocale, localeCodes, i18nConfig };
|
|
233
340
|
|
|
234
341
|
if (import.meta.hot) {
|
|
235
342
|
import.meta.hot.accept((mod) => {
|
|
@@ -244,4 +351,4 @@ if (import.meta.hot) {
|
|
|
244
351
|
});
|
|
245
352
|
}
|
|
246
353
|
//#endregion
|
|
247
|
-
export { createRoutingVirtualModule as a, createPagesVirtualModule as i, createLocalesVirtualModule as n, createMetaVirtualModule as r, createAppVirtualModule as t };
|
|
354
|
+
export { createRoutingVirtualModule as a, createPagesVirtualModule as i, createLocalesVirtualModule as n, stripMacros as o, createMetaVirtualModule as r, transformMacros as s, createAppVirtualModule as t };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/virtual-registry.d.ts
|
|
2
|
+
interface VirtualModuleContext {
|
|
3
|
+
resolveId(id: string): string | undefined;
|
|
4
|
+
load(id: string): string | undefined;
|
|
5
|
+
invalidate(id: string): void;
|
|
6
|
+
}
|
|
7
|
+
type VirtualModuleTransform = (id: string, code: string) => string | Promise<string>;
|
|
8
|
+
interface VirtualModule {
|
|
9
|
+
id: string;
|
|
10
|
+
resolve(id: string, importer?: string): string | undefined;
|
|
11
|
+
load(id?: string): string | Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
declare class VirtualModuleRegistry {
|
|
14
|
+
private modules;
|
|
15
|
+
private invalidated;
|
|
16
|
+
register(mod: VirtualModule): void;
|
|
17
|
+
resolveId(id: string, importer?: string): string | undefined;
|
|
18
|
+
load(id: string): Promise<string | undefined>;
|
|
19
|
+
invalidate(id: string): void;
|
|
20
|
+
isInvalidated(id: string): boolean;
|
|
21
|
+
clearInvalidated(): void;
|
|
22
|
+
getModules(): VirtualModule[];
|
|
23
|
+
clear(): void;
|
|
24
|
+
}
|
|
25
|
+
declare function useVirtualRegistry(): VirtualModuleRegistry;
|
|
26
|
+
declare function resetVirtualRegistry(): void;
|
|
27
|
+
declare function defineVirtualModule(id: string, loader: () => string | Promise<string>): VirtualModule;
|
|
28
|
+
declare function defineVirtualModulePrefix(prefix: string, loader: (id: string) => string | Promise<string>): VirtualModule;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { defineVirtualModule as a, useVirtualRegistry as c, VirtualModuleTransform as i, VirtualModuleContext as n, defineVirtualModulePrefix as o, VirtualModuleRegistry as r, resetVirtualRegistry as s, VirtualModule as t };
|