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