@ubean/build 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +88 -0
- package/dist/index.js +43 -0
- package/dist/production.d.ts +27 -0
- package/dist/production.js +611 -0
- package/dist/virtual-modules-DXIaZdQI.js +404 -0
- package/dist/vite.d.ts +41 -0
- package/dist/vite.js +209 -0
- package/package.json +9 -9
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
//#region src/virtual-registry.ts
|
|
2
|
+
var VirtualModuleRegistry = class {
|
|
3
|
+
modules = /* @__PURE__ */ new Map();
|
|
4
|
+
invalidated = /* @__PURE__ */ new Set();
|
|
5
|
+
register(mod) {
|
|
6
|
+
this.modules.set(mod.id, mod);
|
|
7
|
+
}
|
|
8
|
+
resolveId(id, importer) {
|
|
9
|
+
if (this.modules.has(id)) return id;
|
|
10
|
+
for (const mod of this.modules.values()) {
|
|
11
|
+
const resolved = mod.resolve(id, importer);
|
|
12
|
+
if (resolved) return resolved;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async load(id) {
|
|
16
|
+
const mod = this.modules.get(id);
|
|
17
|
+
if (!mod) return void 0;
|
|
18
|
+
return mod.load();
|
|
19
|
+
}
|
|
20
|
+
invalidate(id) {
|
|
21
|
+
this.invalidated.add(id);
|
|
22
|
+
}
|
|
23
|
+
isInvalidated(id) {
|
|
24
|
+
return this.invalidated.has(id);
|
|
25
|
+
}
|
|
26
|
+
clearInvalidated() {
|
|
27
|
+
this.invalidated.clear();
|
|
28
|
+
}
|
|
29
|
+
getModules() {
|
|
30
|
+
return [...this.modules.values()];
|
|
31
|
+
}
|
|
32
|
+
clear() {
|
|
33
|
+
this.modules.clear();
|
|
34
|
+
this.invalidated.clear();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
let _registry = null;
|
|
38
|
+
function useVirtualRegistry() {
|
|
39
|
+
if (!_registry) _registry = new VirtualModuleRegistry();
|
|
40
|
+
return _registry;
|
|
41
|
+
}
|
|
42
|
+
function resetVirtualRegistry() {
|
|
43
|
+
_registry = null;
|
|
44
|
+
}
|
|
45
|
+
function defineVirtualModule(id, loader) {
|
|
46
|
+
return {
|
|
47
|
+
id,
|
|
48
|
+
resolve(resolvedId) {
|
|
49
|
+
return resolvedId === id ? id : void 0;
|
|
50
|
+
},
|
|
51
|
+
load: loader
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function defineVirtualModulePrefix(prefix, loader) {
|
|
55
|
+
return {
|
|
56
|
+
id: prefix,
|
|
57
|
+
resolve(id) {
|
|
58
|
+
if (id.startsWith(prefix)) return id;
|
|
59
|
+
},
|
|
60
|
+
load(id) {
|
|
61
|
+
return loader(id || prefix);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/macros.ts
|
|
67
|
+
const MACRO_NAMES = ["definePage"];
|
|
68
|
+
function findBalancedCall(code, funcName, startSearch = 0) {
|
|
69
|
+
const pattern = new RegExp(`\\b${funcName}\\s*\\(`, "g");
|
|
70
|
+
pattern.lastIndex = startSearch;
|
|
71
|
+
const match = pattern.exec(code);
|
|
72
|
+
if (!match) return null;
|
|
73
|
+
const startIdx = match.index;
|
|
74
|
+
const parenStart = match.index + match[0].length;
|
|
75
|
+
let depth = 1;
|
|
76
|
+
let i = parenStart;
|
|
77
|
+
let inString = null;
|
|
78
|
+
let escaped = false;
|
|
79
|
+
let inTemplateExpr = 0;
|
|
80
|
+
while (i < code.length && depth > 0) {
|
|
81
|
+
const ch = code[i];
|
|
82
|
+
if (escaped) {
|
|
83
|
+
escaped = false;
|
|
84
|
+
i++;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (ch === "\\") {
|
|
88
|
+
escaped = true;
|
|
89
|
+
i++;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (inString) {
|
|
93
|
+
if (inString === "`" && ch === "$" && code[i + 1] === "{") {
|
|
94
|
+
inTemplateExpr++;
|
|
95
|
+
i += 2;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (inString === "`" && ch === "}" && inTemplateExpr > 0) {
|
|
99
|
+
inTemplateExpr--;
|
|
100
|
+
i++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (ch === inString) inString = null;
|
|
104
|
+
i++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (ch === "\"" || ch === "'" || ch === "`") {
|
|
108
|
+
inString = ch;
|
|
109
|
+
i++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (ch === "/" && code[i + 1] === "/") {
|
|
113
|
+
while (i < code.length && code[i] !== "\n") i++;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (ch === "/" && code[i + 1] === "*") {
|
|
117
|
+
i += 2;
|
|
118
|
+
while (i < code.length && !(code[i] === "*" && code[i + 1] === "/")) i++;
|
|
119
|
+
i += 2;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (ch === "(" || ch === "{" || ch === "[") depth++;
|
|
123
|
+
else if (ch === ")" || ch === "}" || ch === "]") {
|
|
124
|
+
depth--;
|
|
125
|
+
if (depth === 0 && ch === ")") return {
|
|
126
|
+
start: startIdx,
|
|
127
|
+
end: i + 1
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
i++;
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
function stripStatement(code, callStart, callEnd) {
|
|
135
|
+
let start = callStart;
|
|
136
|
+
let end = callEnd;
|
|
137
|
+
while (start > 0 && /[ \t]/.test(code[start - 1])) start--;
|
|
138
|
+
const exportDefaultMatch = code.slice(0, start).match(/export\s+default\s*$/);
|
|
139
|
+
if (exportDefaultMatch) {
|
|
140
|
+
start -= exportDefaultMatch[0].length;
|
|
141
|
+
while (start > 0 && /[ \t\n\r]/.test(code[start - 1])) start--;
|
|
142
|
+
}
|
|
143
|
+
if (code[end] === ";") end++;
|
|
144
|
+
while (end < code.length && /[ \t]/.test(code[end])) end++;
|
|
145
|
+
return code.slice(0, start) + code.slice(end);
|
|
146
|
+
}
|
|
147
|
+
function stripMacros(code, macros = MACRO_NAMES) {
|
|
148
|
+
let result = code;
|
|
149
|
+
for (const macro of macros) {
|
|
150
|
+
let offset = 0;
|
|
151
|
+
while (true) {
|
|
152
|
+
const found = findBalancedCall(result, macro, offset);
|
|
153
|
+
if (!found) break;
|
|
154
|
+
result = stripStatement(result, found.start, found.end);
|
|
155
|
+
offset = found.start;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
function transformMacros(code, id) {
|
|
161
|
+
if (!(id.includes("/src/pages/") || id.includes("/src/routes/") || id.includes("/src/middleware/"))) return null;
|
|
162
|
+
if (id.endsWith(".vue")) return transformVueMacros(code);
|
|
163
|
+
if (/\.(ts|js|mjs|mts|tsx|jsx)$/.test(id)) return stripMacros(code);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
function transformVueMacros(code) {
|
|
167
|
+
return code.replace(/<script([^>]*)>([\s\S]*?)<\/script>/g, (_match, attrs, content) => {
|
|
168
|
+
return `<script${attrs}>${stripMacros(content)}<\/script>`;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/virtual-modules.ts
|
|
173
|
+
function toVitePath(p) {
|
|
174
|
+
return p.replace(/\\/g, "/");
|
|
175
|
+
}
|
|
176
|
+
function createRoutingVirtualModule(routes, middlewares) {
|
|
177
|
+
return defineVirtualModule("ubean:routes", () => {
|
|
178
|
+
return `export const routes = [\n${routes.map((r) => ` { method: ${JSON.stringify(r.method)}, path: ${JSON.stringify(r.path)}, id: ${JSON.stringify(r.id)}, filePath: ${JSON.stringify(r.filePath)} }`).join(",\n")}\n];\nexport const middlewares = [\n${middlewares.map((m) => ` { path: ${JSON.stringify(m.path)}, filePath: ${JSON.stringify(m.filePath)}, order: ${m.order}, global: ${m.global} }`).join(",\n")}\n];\nexport default { routes, middlewares };`;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function createPagesVirtualModule(pages, layouts) {
|
|
182
|
+
return defineVirtualModule("ubean:pages", () => {
|
|
183
|
+
return `export const pages = {\n${pages.map((p) => ` ${JSON.stringify(p.name)}: { name: ${JSON.stringify(p.name)}, path: ${JSON.stringify(p.path)}, filePath: ${JSON.stringify(p.filePath)}, layout: ${JSON.stringify(p.layout)}, reuseTarget: ${JSON.stringify(p.reuseTarget)} }`).join(",\n")}\n};\nexport const layouts = {\n${layouts.map((l) => ` ${JSON.stringify(l.name)}: { name: ${JSON.stringify(l.name)}, filePath: ${JSON.stringify(l.filePath)}, isDefault: ${l.isDefault} }`).join(",\n")}\n};\nexport type RouteName = ${pages.map((p) => JSON.stringify(p.name)).join(" | ") || "string"};\nexport type LayoutName = ${layouts.map((l) => JSON.stringify(l.name)).join(" | ") || "string"};\nexport default { pages, layouts };`;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function createMetaVirtualModule() {
|
|
187
|
+
return defineVirtualModule("ubean:meta", () => {
|
|
188
|
+
return `export const UBEAN_VERSION = "0.0.1";\nexport default { version: UBEAN_VERSION };`;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function createAppVirtualModule(apiRoutes, middlewares, pages, srcDir) {
|
|
192
|
+
return defineVirtualModule("ubean:app-config", () => {
|
|
193
|
+
const routesJson = apiRoutes.map((r) => JSON.stringify({
|
|
194
|
+
relativePath: r.relativePath,
|
|
195
|
+
route: r.route,
|
|
196
|
+
method: r.method,
|
|
197
|
+
env: r.env
|
|
198
|
+
}));
|
|
199
|
+
const middlewareJson = middlewares.map((m) => JSON.stringify({
|
|
200
|
+
relativePath: m.relativePath,
|
|
201
|
+
order: m.order,
|
|
202
|
+
global: m.global
|
|
203
|
+
}));
|
|
204
|
+
const pagesJson = pages.map((p) => JSON.stringify({
|
|
205
|
+
relativePath: p.relativePath,
|
|
206
|
+
name: p.name,
|
|
207
|
+
route: p.route,
|
|
208
|
+
isReuse: p.isReuse,
|
|
209
|
+
layout: p.layout
|
|
210
|
+
}));
|
|
211
|
+
const viteSrcDir = toVitePath(srcDir);
|
|
212
|
+
const prefix = viteSrcDir === "/" || viteSrcDir === "" ? "" : viteSrcDir;
|
|
213
|
+
return `
|
|
214
|
+
const routeModules = import.meta.glob([${JSON.stringify(`${prefix}/routes/**/*.{ts,js,mjs}`)}], { eager: false });
|
|
215
|
+
const middlewareModules = import.meta.glob([${JSON.stringify(`${prefix}/middleware/**/*.{ts,js,mjs}`)}], { eager: false });
|
|
216
|
+
const pageModules = import.meta.glob([${JSON.stringify(`${prefix}/pages/**/*.{vue,ts,tsx,js,jsx}`)}], { eager: false });
|
|
217
|
+
|
|
218
|
+
const _srcPrefix = ${JSON.stringify(prefix)};
|
|
219
|
+
function normalizeKey(p) {
|
|
220
|
+
const prefixes = ['/routes/', '/middleware/', '/pages/'];
|
|
221
|
+
for (const pf of prefixes) {
|
|
222
|
+
const fullPf = _srcPrefix + pf;
|
|
223
|
+
if (p.includes(fullPf)) {
|
|
224
|
+
const idx = p.indexOf(fullPf);
|
|
225
|
+
return p.slice(idx + fullPf.length);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return p;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export const routeLoaders = {};
|
|
232
|
+
for (const [key, loader] of Object.entries(routeModules)) {
|
|
233
|
+
routeLoaders[normalizeKey(key)] = loader;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export const middlewareLoaders = {};
|
|
237
|
+
for (const [key, loader] of Object.entries(middlewareModules)) {
|
|
238
|
+
middlewareLoaders[normalizeKey(key)] = loader;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export const pageLoaders = {};
|
|
242
|
+
for (const [key, loader] of Object.entries(pageModules)) {
|
|
243
|
+
pageLoaders[normalizeKey(key)] = loader;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export const apiRoutes = [${routesJson.join(",")}];
|
|
247
|
+
export const middlewares = [${middlewareJson.join(",")}];
|
|
248
|
+
export const pages = [${pagesJson.join(",")}];
|
|
249
|
+
|
|
250
|
+
export default { routeLoaders, middlewareLoaders, pageLoaders, apiRoutes, middlewares, pages };
|
|
251
|
+
`;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function createLocalesVirtualModule(_locales, defaultLocale, srcDir = "src") {
|
|
255
|
+
return defineVirtualModule("ubean:locales", () => {
|
|
256
|
+
const defaultCode = JSON.stringify(defaultLocale);
|
|
257
|
+
const viteSrcDir = toVitePath(srcDir);
|
|
258
|
+
const prefix = viteSrcDir === "/" || viteSrcDir === "" ? "" : viteSrcDir;
|
|
259
|
+
return `
|
|
260
|
+
import { defineLocale, setLocale, mergeLocale } from 'ubean/runtime/i18n';
|
|
261
|
+
|
|
262
|
+
const localeModules = import.meta.glob([${JSON.stringify(`${prefix}/locales/**/*.{json,json5,yaml,yml,js,mjs,cjs,ts,mts,cts}`)}], { eager: false });
|
|
263
|
+
|
|
264
|
+
function parseLocalePath(path) {
|
|
265
|
+
const withoutPrefix = path.replace(${JSON.stringify(`${prefix || ""}/locales/`)}, '');
|
|
266
|
+
const lastDot = withoutPrefix.lastIndexOf('.');
|
|
267
|
+
const withoutExt = lastDot > 0 ? withoutPrefix.slice(0, lastDot) : withoutPrefix;
|
|
268
|
+
const parts = withoutExt.split('/');
|
|
269
|
+
const fileName = parts[parts.length - 1];
|
|
270
|
+
const orderMatch = fileName.match(/^(\\d+)\\.(.+)$/);
|
|
271
|
+
let code = orderMatch ? orderMatch[2] : fileName;
|
|
272
|
+
let isDefault = code === 'default' || fileName.startsWith('default.');
|
|
273
|
+
const dirParts = parts.slice(0, -1);
|
|
274
|
+
|
|
275
|
+
let namespace;
|
|
276
|
+
if (dirParts.length > 0) {
|
|
277
|
+
const fileNameCode = code === 'index' ? undefined : code;
|
|
278
|
+
code = fileNameCode || dirParts[dirParts.length - 1];
|
|
279
|
+
namespace = dirParts.join('.');
|
|
280
|
+
if (fileNameCode && fileNameCode !== code) {
|
|
281
|
+
namespace = dirParts.join('.') + '.' + fileNameCode;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (isDefault && code === 'default') {
|
|
286
|
+
code = 'en';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return { code, namespace, isDefault };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function setNestedValue(obj, path, value) {
|
|
293
|
+
let current = obj;
|
|
294
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
295
|
+
const key = path[i];
|
|
296
|
+
if (!current[key]) current[key] = {};
|
|
297
|
+
current = current[key];
|
|
298
|
+
}
|
|
299
|
+
current[path[path.length - 1]] = value;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
let loaded = false;
|
|
303
|
+
|
|
304
|
+
export async function loadLocales() {
|
|
305
|
+
if (loaded) return;
|
|
306
|
+
loaded = true;
|
|
307
|
+
|
|
308
|
+
const localeData = new Map();
|
|
309
|
+
|
|
310
|
+
for (const [path, loader] of Object.entries(localeModules)) {
|
|
311
|
+
try {
|
|
312
|
+
const { code, namespace, isDefault } = parseLocalePath(path);
|
|
313
|
+
const mod = await loader();
|
|
314
|
+
const data = mod.default || mod;
|
|
315
|
+
const hasMeta = typeof data.name === 'string' || data.dir === 'ltr' || data.dir === 'rtl' || typeof data.isDefault === 'boolean';
|
|
316
|
+
const isWrapper = hasMeta && typeof data.messages === 'object' && data.messages !== null;
|
|
317
|
+
const messages = isWrapper ? data.messages : data;
|
|
318
|
+
const options = {
|
|
319
|
+
name: isWrapper ? data.name : undefined,
|
|
320
|
+
dir: isWrapper ? (data.dir || 'ltr') : 'ltr',
|
|
321
|
+
isDefault: isDefault || (isWrapper ? data.isDefault : false)
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
if (!localeData.has(code)) {
|
|
325
|
+
localeData.set(code, { messages: {}, options: { isDefault: options.isDefault || isDefault, name: options.name, dir: options.dir } });
|
|
326
|
+
}
|
|
327
|
+
const entry = localeData.get(code);
|
|
328
|
+
if (options.name) entry.options.name = options.name;
|
|
329
|
+
if (options.dir) entry.options.dir = options.dir;
|
|
330
|
+
if (options.isDefault) entry.options.isDefault = true;
|
|
331
|
+
|
|
332
|
+
if (namespace) {
|
|
333
|
+
setNestedValue(entry.messages, namespace.split('.'), messages);
|
|
334
|
+
} else {
|
|
335
|
+
Object.assign(entry.messages, messages);
|
|
336
|
+
}
|
|
337
|
+
} catch (e) {
|
|
338
|
+
console.warn('[ubean] Failed to load locale:', path, e);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
for (const [code, { messages, options }] of localeData.entries()) {
|
|
343
|
+
defineLocale({
|
|
344
|
+
code,
|
|
345
|
+
messages,
|
|
346
|
+
name: options.name,
|
|
347
|
+
dir: options.dir,
|
|
348
|
+
isDefault: options.isDefault
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const defaultCode = ${defaultCode};
|
|
353
|
+
if (defaultCode && localeData.has(defaultCode)) {
|
|
354
|
+
setLocale(defaultCode);
|
|
355
|
+
} else if (localeData.size > 0) {
|
|
356
|
+
const firstDefault = [...localeData.entries()].find(([, v]) => v.options.isDefault);
|
|
357
|
+
if (firstDefault) {
|
|
358
|
+
setLocale(firstDefault[0]);
|
|
359
|
+
} else {
|
|
360
|
+
setLocale([...localeData.keys()][0]);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export async function reloadLocale(path) {
|
|
366
|
+
if (!path.includes('/locales/')) return;
|
|
367
|
+
try {
|
|
368
|
+
const loader = localeModules[path];
|
|
369
|
+
if (loader) {
|
|
370
|
+
const { code, namespace } = parseLocalePath(path);
|
|
371
|
+
const mod = await loader();
|
|
372
|
+
const data = mod.default || mod;
|
|
373
|
+
const hasMeta = typeof data.name === 'string' || data.dir === 'ltr' || data.dir === 'rtl' || typeof data.isDefault === 'boolean';
|
|
374
|
+
const isWrapper = hasMeta && typeof data.messages === 'object' && data.messages !== null;
|
|
375
|
+
const messages = isWrapper ? data.messages : data;
|
|
376
|
+
const merged = {};
|
|
377
|
+
if (namespace) {
|
|
378
|
+
setNestedValue(merged, namespace.split('.'), messages);
|
|
379
|
+
} else {
|
|
380
|
+
Object.assign(merged, messages);
|
|
381
|
+
}
|
|
382
|
+
mergeLocale(code, merged);
|
|
383
|
+
}
|
|
384
|
+
} catch (e) {
|
|
385
|
+
console.warn('[ubean] Failed to reload locale:', path, e);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export default { loadLocales, reloadLocale };
|
|
390
|
+
|
|
391
|
+
if (import.meta.hot) {
|
|
392
|
+
import.meta.hot.accept((mod) => {
|
|
393
|
+
if (mod) {
|
|
394
|
+
for (const path of Object.keys(localeModules)) {
|
|
395
|
+
mod.reloadLocale(path);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
`;
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
//#endregion
|
|
404
|
+
export { createRoutingVirtualModule as a, VirtualModuleRegistry as c, resetVirtualRegistry as d, useVirtualRegistry as f, createPagesVirtualModule as i, defineVirtualModule as l, createLocalesVirtualModule as n, stripMacros as o, createMetaVirtualModule as r, transformMacros as s, createAppVirtualModule as t, defineVirtualModulePrefix as u };
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ResolvedConfig } from "@ubean/config";
|
|
2
|
+
import { Plugin } from "vite";
|
|
3
|
+
//#region src/vite.d.ts
|
|
4
|
+
interface UbeanPluginOptions {
|
|
5
|
+
/**
|
|
6
|
+
* 已解析的 ubean 配置。如果未提供,插件会按以下顺序获取:
|
|
7
|
+
* 1. 尝试从全局缓存读取(`loadUbeanConfig` 已被 CLI 调用时会有缓存)
|
|
8
|
+
* 2. 在 `buildStart` 中异步调用 `loadUbeanConfig()` 加载
|
|
9
|
+
*/
|
|
10
|
+
config?: ResolvedConfig;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* ubean 核心 Vite 插件(框架无关部分)。
|
|
14
|
+
*
|
|
15
|
+
* 提供:
|
|
16
|
+
* - 虚拟模块(`ubean:routes`、`ubean:pages`、`ubean:meta`、`ubean:app-config`、`ubean:locales`)
|
|
17
|
+
* - 宏转换(`definePage` / `defineMeta` 在 `.ts` / `.vue` 中被剥离)
|
|
18
|
+
* - 文件监听(dev 模式下扫描 `routes` / `middleware` / `pages` / `layouts` / `plugins` / `locales`)
|
|
19
|
+
* - 实体路由文件生成(当 `routing.mode` 为 `'file'` 或 `'both'` 时触发 `@ubean/routing/generator`)
|
|
20
|
+
*
|
|
21
|
+
* Vue 专属的虚拟模块(`virtual:ubean-pages`、`virtual:ubean-app` 等)由 `@ubean/vite` 的
|
|
22
|
+
* `ubeanVuePlugin` 提供,二者共用 `useVirtualRegistry()` 注册表。
|
|
23
|
+
*
|
|
24
|
+
* @example 在 vite.config.ts 中使用(自动加载 ubean.config)
|
|
25
|
+
* ```typescript
|
|
26
|
+
* import { defineConfig } from 'vite';
|
|
27
|
+
* import { ubeanPlugin } from '@ubean/build/vite';
|
|
28
|
+
*
|
|
29
|
+
* export default defineConfig({
|
|
30
|
+
* plugins: [ubeanPlugin()]
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example 显式传入配置(ubean dev/build 内部使用)
|
|
35
|
+
* ```typescript
|
|
36
|
+
* ubeanPlugin({ config: resolvedConfig })
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function ubeanPlugin(options?: UbeanPluginOptions): Plugin;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { UbeanPluginOptions, ubeanPlugin };
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { a as createRoutingVirtualModule, f as useVirtualRegistry, i as createPagesVirtualModule, n as createLocalesVirtualModule, r as createMetaVirtualModule, s as transformMacros, t as createAppVirtualModule } from "./virtual-modules-DXIaZdQI.js";
|
|
2
|
+
import { loadUbeanConfig, tryGetConfig } from "@ubean/config";
|
|
3
|
+
import { createUbeanRouter, scanProject } from "@ubean/routing";
|
|
4
|
+
import { join, relative, resolve } from "pathe";
|
|
5
|
+
//#region src/vite.ts
|
|
6
|
+
const VIRTUAL_MODULES = [
|
|
7
|
+
"ubean:routes",
|
|
8
|
+
"ubean:pages",
|
|
9
|
+
"ubean:meta",
|
|
10
|
+
"ubean:app-config",
|
|
11
|
+
"ubean:locales"
|
|
12
|
+
];
|
|
13
|
+
const VIRTUAL_PREFIX = "\0ubean:";
|
|
14
|
+
/**
|
|
15
|
+
* ubean 核心 Vite 插件(框架无关部分)。
|
|
16
|
+
*
|
|
17
|
+
* 提供:
|
|
18
|
+
* - 虚拟模块(`ubean:routes`、`ubean:pages`、`ubean:meta`、`ubean:app-config`、`ubean:locales`)
|
|
19
|
+
* - 宏转换(`definePage` / `defineMeta` 在 `.ts` / `.vue` 中被剥离)
|
|
20
|
+
* - 文件监听(dev 模式下扫描 `routes` / `middleware` / `pages` / `layouts` / `plugins` / `locales`)
|
|
21
|
+
* - 实体路由文件生成(当 `routing.mode` 为 `'file'` 或 `'both'` 时触发 `@ubean/routing/generator`)
|
|
22
|
+
*
|
|
23
|
+
* Vue 专属的虚拟模块(`virtual:ubean-pages`、`virtual:ubean-app` 等)由 `@ubean/vite` 的
|
|
24
|
+
* `ubeanVuePlugin` 提供,二者共用 `useVirtualRegistry()` 注册表。
|
|
25
|
+
*
|
|
26
|
+
* @example 在 vite.config.ts 中使用(自动加载 ubean.config)
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { defineConfig } from 'vite';
|
|
29
|
+
* import { ubeanPlugin } from '@ubean/build/vite';
|
|
30
|
+
*
|
|
31
|
+
* export default defineConfig({
|
|
32
|
+
* plugins: [ubeanPlugin()]
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example 显式传入配置(ubean dev/build 内部使用)
|
|
37
|
+
* ```typescript
|
|
38
|
+
* ubeanPlugin({ config: resolvedConfig })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
function ubeanPlugin(options) {
|
|
42
|
+
const virtualRegistry = useVirtualRegistry();
|
|
43
|
+
let ubeanConfig = options?.config ?? tryGetConfig() ?? void 0;
|
|
44
|
+
let srcDirAbs = "";
|
|
45
|
+
let viteSrcDir = "";
|
|
46
|
+
let viteSrcPrefix = "";
|
|
47
|
+
function ensureDerived() {
|
|
48
|
+
if (!ubeanConfig) return;
|
|
49
|
+
srcDirAbs = resolve(ubeanConfig.rootDir, ubeanConfig.srcDir);
|
|
50
|
+
viteSrcDir = relative(ubeanConfig.rootDir, srcDirAbs).replace(/\\/g, "/");
|
|
51
|
+
viteSrcPrefix = viteSrcDir ? `/${viteSrcDir}` : "";
|
|
52
|
+
}
|
|
53
|
+
if (ubeanConfig) ensureDerived();
|
|
54
|
+
return {
|
|
55
|
+
name: "ubean:core",
|
|
56
|
+
enforce: "pre",
|
|
57
|
+
async buildStart() {
|
|
58
|
+
if (!ubeanConfig) {
|
|
59
|
+
ubeanConfig = await loadUbeanConfig();
|
|
60
|
+
ensureDerived();
|
|
61
|
+
}
|
|
62
|
+
await scanAndRegister();
|
|
63
|
+
},
|
|
64
|
+
resolveId(id) {
|
|
65
|
+
if (VIRTUAL_MODULES.includes(id)) return VIRTUAL_PREFIX + id;
|
|
66
|
+
},
|
|
67
|
+
async load(id) {
|
|
68
|
+
if (id.startsWith(VIRTUAL_PREFIX)) {
|
|
69
|
+
const moduleId = id.slice(7);
|
|
70
|
+
const mod = virtualRegistry.getModules().find((m) => m.id === moduleId);
|
|
71
|
+
if (mod) return await mod.load();
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
transform(code, id) {
|
|
75
|
+
const result = transformMacros(code, id);
|
|
76
|
+
if (result !== null && result !== code) return {
|
|
77
|
+
code: result,
|
|
78
|
+
map: null
|
|
79
|
+
};
|
|
80
|
+
return null;
|
|
81
|
+
},
|
|
82
|
+
configureServer(server) {
|
|
83
|
+
const watchDirs = [
|
|
84
|
+
"routes",
|
|
85
|
+
"middleware",
|
|
86
|
+
"pages",
|
|
87
|
+
"layouts",
|
|
88
|
+
"plugins",
|
|
89
|
+
"locales"
|
|
90
|
+
];
|
|
91
|
+
const config = ubeanConfig;
|
|
92
|
+
const srcDir = join(config.rootDir, config.srcDir);
|
|
93
|
+
for (const dir of watchDirs) server.watcher.add(join(srcDir, dir));
|
|
94
|
+
server.watcher.on("add", handleFileChange);
|
|
95
|
+
server.watcher.on("unlink", handleFileChange);
|
|
96
|
+
server.watcher.on("change", handleFileChange);
|
|
97
|
+
async function handleFileChange(file) {
|
|
98
|
+
const relativePath = file.replace(`${srcDir}/`, "");
|
|
99
|
+
if (watchDirs.some((d) => relativePath.startsWith(`${d}/`))) {
|
|
100
|
+
await scanAndRegister();
|
|
101
|
+
for (const mod of VIRTUAL_MODULES) {
|
|
102
|
+
const module = server.moduleGraph.getModuleById(VIRTUAL_PREFIX + mod);
|
|
103
|
+
if (module) server.moduleGraph.invalidateModule(module);
|
|
104
|
+
}
|
|
105
|
+
if (relativePath.startsWith("locales/")) server.ws.send({
|
|
106
|
+
type: "custom",
|
|
107
|
+
event: "ubean:locale-update",
|
|
108
|
+
data: { file }
|
|
109
|
+
});
|
|
110
|
+
else server.ws.send({ type: "full-reload" });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
async function scanAndRegister() {
|
|
116
|
+
if (!ubeanConfig) return;
|
|
117
|
+
const result = await scanProject({
|
|
118
|
+
cwd: ubeanConfig.rootDir,
|
|
119
|
+
srcDir: ubeanConfig.srcDir,
|
|
120
|
+
dirs: ubeanConfig.dir,
|
|
121
|
+
ignore: ubeanConfig.scanOptions?.ignore
|
|
122
|
+
});
|
|
123
|
+
const router = createUbeanRouter();
|
|
124
|
+
for (const mw of result.middlewares) router.addMiddleware(mw);
|
|
125
|
+
for (const route of result.apiRoutes) router.addApiRoute(route);
|
|
126
|
+
for (const page of result.pages) router.addPage(page);
|
|
127
|
+
for (const layout of result.layouts) router.addLayout(layout);
|
|
128
|
+
virtualRegistry.register(createRoutingVirtualModule(result.apiRoutes.map((r) => ({
|
|
129
|
+
method: r.method?.toUpperCase() || "ALL",
|
|
130
|
+
path: r.route,
|
|
131
|
+
id: `${r.method}:${r.route}`,
|
|
132
|
+
filePath: r.fullPath
|
|
133
|
+
})), result.middlewares.map((m) => ({
|
|
134
|
+
path: "/**",
|
|
135
|
+
filePath: m.fullPath,
|
|
136
|
+
order: m.order,
|
|
137
|
+
global: m.global
|
|
138
|
+
}))));
|
|
139
|
+
virtualRegistry.register(createPagesVirtualModule(result.pages.map((p) => ({
|
|
140
|
+
name: p.name,
|
|
141
|
+
path: p.route,
|
|
142
|
+
filePath: p.fullPath,
|
|
143
|
+
layout: p.layout,
|
|
144
|
+
reuseTarget: p.reuseTarget
|
|
145
|
+
})), result.layouts.map((l) => ({
|
|
146
|
+
name: l.name,
|
|
147
|
+
filePath: l.fullPath,
|
|
148
|
+
isDefault: l.isDefault
|
|
149
|
+
}))));
|
|
150
|
+
virtualRegistry.register(createMetaVirtualModule());
|
|
151
|
+
virtualRegistry.register(createAppVirtualModule(result.apiRoutes, result.middlewares, result.pages, viteSrcPrefix || "/"));
|
|
152
|
+
virtualRegistry.register(createLocalesVirtualModule(result.locales, result.defaultLocale, viteSrcPrefix || "/"));
|
|
153
|
+
await maybeGenerateRouteFiles(ubeanConfig, result).catch((err) => {
|
|
154
|
+
console.warn("[ubean:core] Route file generation failed:", err?.message || err);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 根据 `routing.mode` 决定是否触发实体文件生成。
|
|
160
|
+
*
|
|
161
|
+
* - `'virtual'`(默认):仅生成 `.ubean/typed-router.d.ts`(类型声明,所有模式都生成)
|
|
162
|
+
* - `'file'`:额外生成 `routes.ts`/`imports.ts` 到 `outputDir`(实体文件,可编辑 `meta`)
|
|
163
|
+
* - `'both'`:同 `'file'`,且虚拟模块也加载实体文件
|
|
164
|
+
*
|
|
165
|
+
* `typed-router.d.ts` 包含 `@ubean/routing` 和 `vue-router`/`vue-router/auto-routes`
|
|
166
|
+
* 的模块增强(让 `useRoute<Name>(name)` 能推断 `route.params` 类型),所有模式
|
|
167
|
+
* 都会生成到 `.ubean/typed-router.d.ts`,与 `auto-imports.d.ts`/`components.d.ts`
|
|
168
|
+
* 等其他纯类型声明产物同目录,由 `.gitignore` 忽略。
|
|
169
|
+
*
|
|
170
|
+
* 由于 `@ubean/routing/generator` 通过动态 import 加载,前端-only 项目
|
|
171
|
+
* (不依赖实体路由文件)即使没有安装 generator 相关依赖也能运行。
|
|
172
|
+
*
|
|
173
|
+
* 注意:`@ubean/config` 与 `@ubean/routing/generator` 的 `getRouteMeta` /
|
|
174
|
+
* `onGenerated` 签名略有差异(配置层面向用户,生成器层面向内部)。本函数
|
|
175
|
+
* 负责适配:把 `(filePath, frontmatter) => meta` 包装为 `(page) => meta`,
|
|
176
|
+
* 把 `GeneratorResult` 转换为 `string[]` 文件路径列表。
|
|
177
|
+
*/
|
|
178
|
+
async function maybeGenerateRouteFiles(config, scanResult) {
|
|
179
|
+
const mode = config.routing?.mode;
|
|
180
|
+
const generateEntityFiles = mode === "file" || mode === "both";
|
|
181
|
+
const routing = config.routing;
|
|
182
|
+
const outDir = resolve(config.rootDir, routing.outputDir);
|
|
183
|
+
const dtsPath = resolve(config.rootDir, ".ubean", "typed-router.d.ts");
|
|
184
|
+
const configGetRouteMeta = routing.getRouteMeta;
|
|
185
|
+
const generatorGetRouteMeta = configGetRouteMeta ? (page) => configGetRouteMeta(page.relativePath, page.frontmatter ?? {}) : void 0;
|
|
186
|
+
const { generateRouteFiles } = await import("@ubean/routing/generator");
|
|
187
|
+
const result = await generateRouteFiles(scanResult, {
|
|
188
|
+
cwd: config.rootDir,
|
|
189
|
+
outDir,
|
|
190
|
+
dtsPath,
|
|
191
|
+
generateRoutes: generateEntityFiles,
|
|
192
|
+
generateImports: generateEntityFiles,
|
|
193
|
+
generateDts: true,
|
|
194
|
+
routeLazy: routing.routeLazy,
|
|
195
|
+
layoutLazy: routing.layoutLazy,
|
|
196
|
+
getRouteMeta: generatorGetRouteMeta,
|
|
197
|
+
headerComment: void 0
|
|
198
|
+
});
|
|
199
|
+
if (routing.onGenerated) {
|
|
200
|
+
const files = [
|
|
201
|
+
result.routesPath,
|
|
202
|
+
result.importsPath,
|
|
203
|
+
result.dtsPath
|
|
204
|
+
].filter((p) => Boolean(p));
|
|
205
|
+
routing.onGenerated(files);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
export { ubeanPlugin };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ubean/build",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Core Vite plugin (framework-agnostic) for ubean (ubeanPlugin, virtual modules: routes/pages/meta/app-config/locales, macros)",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -28,14 +28,14 @@
|
|
|
28
28
|
"consola": "^3.4.2",
|
|
29
29
|
"oxc-transform": "^0.141.0",
|
|
30
30
|
"pathe": "^2.0.3",
|
|
31
|
-
"@ubean/config": "0.1.
|
|
32
|
-
"@ubean/islands": "0.1.
|
|
33
|
-
"@ubean/modules": "0.1.
|
|
34
|
-
"@ubean/
|
|
35
|
-
"@ubean/
|
|
36
|
-
"@ubean/
|
|
37
|
-
"@ubean/
|
|
38
|
-
"@ubean/
|
|
31
|
+
"@ubean/config": "0.1.3",
|
|
32
|
+
"@ubean/islands": "0.1.3",
|
|
33
|
+
"@ubean/modules": "0.1.3",
|
|
34
|
+
"@ubean/preset": "0.1.3",
|
|
35
|
+
"@ubean/ssr": "0.1.3",
|
|
36
|
+
"@ubean/utils": "0.1.3",
|
|
37
|
+
"@ubean/routing": "0.1.3",
|
|
38
|
+
"@ubean/vite": "0.1.3"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^26.1.1",
|