@netrojs/vono 0.0.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/LICENSE +21 -0
- package/README.md +768 -0
- package/client.ts +309 -0
- package/core.ts +151 -0
- package/dist/client.d.ts +199 -0
- package/dist/client.js +287 -0
- package/dist/core.d.ts +167 -0
- package/dist/core.js +96 -0
- package/dist/server.d.ts +212 -0
- package/dist/server.js +451 -0
- package/dist/types.d.ts +120 -0
- package/package.json +103 -0
- package/server.ts +590 -0
- package/types.ts +149 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// client.ts
|
|
2
|
+
import {
|
|
3
|
+
createSSRApp,
|
|
4
|
+
defineAsyncComponent,
|
|
5
|
+
defineComponent,
|
|
6
|
+
h,
|
|
7
|
+
inject,
|
|
8
|
+
reactive,
|
|
9
|
+
readonly
|
|
10
|
+
} from "vue";
|
|
11
|
+
import {
|
|
12
|
+
createRouter,
|
|
13
|
+
createWebHistory,
|
|
14
|
+
RouterView
|
|
15
|
+
} from "vue-router";
|
|
16
|
+
|
|
17
|
+
// types.ts
|
|
18
|
+
var SPA_HEADER = "x-vono-spa";
|
|
19
|
+
var STATE_KEY = "__VONO_STATE__";
|
|
20
|
+
var PARAMS_KEY = "__VONO_PARAMS__";
|
|
21
|
+
var SEO_KEY = "__VONO_SEO__";
|
|
22
|
+
var DATA_KEY = /* @__PURE__ */ Symbol.for("vono:data");
|
|
23
|
+
|
|
24
|
+
// core.ts
|
|
25
|
+
var VUE_BRANDS = ["__name", "__file", "__vccOpts", "setup", "render", "data", "components"];
|
|
26
|
+
function isAsyncLoader(c) {
|
|
27
|
+
if (typeof c !== "function") return false;
|
|
28
|
+
const f = c;
|
|
29
|
+
for (const brand of VUE_BRANDS) {
|
|
30
|
+
if (brand in f) return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
function definePage(def) {
|
|
35
|
+
return { __type: "page", ...def };
|
|
36
|
+
}
|
|
37
|
+
function defineGroup(def) {
|
|
38
|
+
return { __type: "group", ...def };
|
|
39
|
+
}
|
|
40
|
+
function defineLayout(component) {
|
|
41
|
+
return { __type: "layout", component };
|
|
42
|
+
}
|
|
43
|
+
function defineApiRoute(path, register) {
|
|
44
|
+
return { __type: "api", path, register };
|
|
45
|
+
}
|
|
46
|
+
function compilePath(path) {
|
|
47
|
+
const keys = [];
|
|
48
|
+
const src = path.replace(/\[\.\.\.([^\]]+)\]/g, (_, k) => {
|
|
49
|
+
keys.push(k);
|
|
50
|
+
return "(.*)";
|
|
51
|
+
}).replace(/\[([^\]]+)\]/g, (_, k) => {
|
|
52
|
+
keys.push(k);
|
|
53
|
+
return "([^/]+)";
|
|
54
|
+
}).replace(/\*/g, "(.*)");
|
|
55
|
+
return { re: new RegExp(`^${src}$`), keys };
|
|
56
|
+
}
|
|
57
|
+
function matchPath(cp, pathname) {
|
|
58
|
+
const m = pathname.match(cp.re);
|
|
59
|
+
if (!m) return null;
|
|
60
|
+
const params = {};
|
|
61
|
+
cp.keys.forEach((k, i) => {
|
|
62
|
+
params[k] = decodeURIComponent(m[i + 1] ?? "");
|
|
63
|
+
});
|
|
64
|
+
return params;
|
|
65
|
+
}
|
|
66
|
+
function toVueRouterPath(vonoPath) {
|
|
67
|
+
return vonoPath.replace(/\[\.\.\.([^\]]+)\]/g, ":$1(.*)*").replace(/\[([^\]]+)\]/g, ":$1");
|
|
68
|
+
}
|
|
69
|
+
function resolveRoutes(routes, options = {}) {
|
|
70
|
+
const pages = [];
|
|
71
|
+
const apis = [];
|
|
72
|
+
for (const route of routes) {
|
|
73
|
+
if (route.__type === "api") {
|
|
74
|
+
apis.push({ ...route, path: (options.prefix ?? "") + route.path });
|
|
75
|
+
} else if (route.__type === "group") {
|
|
76
|
+
const prefix = (options.prefix ?? "") + route.prefix;
|
|
77
|
+
const mw = [...options.middleware ?? [], ...route.middleware ?? []];
|
|
78
|
+
const layout = route.layout !== void 0 ? route.layout : options.layout;
|
|
79
|
+
const sub = resolveRoutes(route.routes, {
|
|
80
|
+
prefix,
|
|
81
|
+
middleware: mw,
|
|
82
|
+
...layout !== void 0 && { layout }
|
|
83
|
+
});
|
|
84
|
+
pages.push(...sub.pages);
|
|
85
|
+
apis.push(...sub.apis);
|
|
86
|
+
} else {
|
|
87
|
+
pages.push({
|
|
88
|
+
fullPath: (options.prefix ?? "") + route.path,
|
|
89
|
+
page: route,
|
|
90
|
+
layout: route.layout !== void 0 ? route.layout : options.layout,
|
|
91
|
+
middleware: [...options.middleware ?? [], ...route.middleware ?? []]
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { pages, apis };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// client.ts
|
|
99
|
+
import {
|
|
100
|
+
useRoute,
|
|
101
|
+
useRouter,
|
|
102
|
+
RouterLink,
|
|
103
|
+
RouterView as RouterView2
|
|
104
|
+
} from "vue-router";
|
|
105
|
+
function setMeta(selector, attr, val) {
|
|
106
|
+
if (!val) {
|
|
107
|
+
document.querySelector(selector)?.remove();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
let el = document.querySelector(selector);
|
|
111
|
+
if (!el) {
|
|
112
|
+
el = document.createElement("meta");
|
|
113
|
+
const [, attrName = "", attrVal = ""] = /\[([^=]+)="([^"]+)"\]/.exec(selector) ?? [];
|
|
114
|
+
if (attrName) el.setAttribute(attrName, attrVal);
|
|
115
|
+
document.head.appendChild(el);
|
|
116
|
+
}
|
|
117
|
+
el.setAttribute(attr, val);
|
|
118
|
+
}
|
|
119
|
+
function syncSEO(seo) {
|
|
120
|
+
if (seo.title) document.title = seo.title;
|
|
121
|
+
setMeta('[name="description"]', "content", seo.description);
|
|
122
|
+
setMeta('[name="keywords"]', "content", seo.keywords);
|
|
123
|
+
setMeta('[name="robots"]', "content", seo.robots);
|
|
124
|
+
setMeta('[name="theme-color"]', "content", seo.themeColor);
|
|
125
|
+
setMeta('[property="og:title"]', "content", seo.ogTitle);
|
|
126
|
+
setMeta('[property="og:description"]', "content", seo.ogDescription);
|
|
127
|
+
setMeta('[property="og:image"]', "content", seo.ogImage);
|
|
128
|
+
setMeta('[property="og:url"]', "content", seo.ogUrl);
|
|
129
|
+
setMeta('[property="og:type"]', "content", seo.ogType);
|
|
130
|
+
setMeta('[name="twitter:card"]', "content", seo.twitterCard);
|
|
131
|
+
setMeta('[name="twitter:title"]', "content", seo.twitterTitle);
|
|
132
|
+
setMeta('[name="twitter:description"]', "content", seo.twitterDescription);
|
|
133
|
+
setMeta('[name="twitter:image"]', "content", seo.twitterImage);
|
|
134
|
+
let link = document.querySelector('link[rel="canonical"]');
|
|
135
|
+
if (seo.canonical) {
|
|
136
|
+
if (!link) {
|
|
137
|
+
link = document.createElement("link");
|
|
138
|
+
link.rel = "canonical";
|
|
139
|
+
document.head.appendChild(link);
|
|
140
|
+
}
|
|
141
|
+
link.href = seo.canonical;
|
|
142
|
+
} else {
|
|
143
|
+
link?.remove();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
var _fetchCache = /* @__PURE__ */ new Map();
|
|
147
|
+
function fetchSPA(href) {
|
|
148
|
+
if (!_fetchCache.has(href)) {
|
|
149
|
+
_fetchCache.set(
|
|
150
|
+
href,
|
|
151
|
+
fetch(href, { headers: { [SPA_HEADER]: "1" } }).then((r) => {
|
|
152
|
+
if (!r.ok) throw new Error(`[vono] ${r.status} ${r.statusText} \u2014 ${href}`);
|
|
153
|
+
return r.json();
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return _fetchCache.get(href);
|
|
158
|
+
}
|
|
159
|
+
function prefetch(url) {
|
|
160
|
+
try {
|
|
161
|
+
const u = new URL(url, location.origin);
|
|
162
|
+
if (u.origin === location.origin) fetchSPA(u.toString());
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
var _mw = [];
|
|
167
|
+
function useClientMiddleware(mw) {
|
|
168
|
+
_mw.push(mw);
|
|
169
|
+
}
|
|
170
|
+
async function runMw(url, done) {
|
|
171
|
+
const chain = [
|
|
172
|
+
..._mw,
|
|
173
|
+
async (_, next) => {
|
|
174
|
+
await done();
|
|
175
|
+
await next();
|
|
176
|
+
}
|
|
177
|
+
];
|
|
178
|
+
let i = 0;
|
|
179
|
+
const run = async () => {
|
|
180
|
+
const fn = chain[i++];
|
|
181
|
+
if (fn) await fn(url, run);
|
|
182
|
+
};
|
|
183
|
+
await run();
|
|
184
|
+
}
|
|
185
|
+
var _pageData = reactive({});
|
|
186
|
+
function updatePageData(newData) {
|
|
187
|
+
for (const k of Object.keys(_pageData)) {
|
|
188
|
+
if (!(k in newData)) delete _pageData[k];
|
|
189
|
+
}
|
|
190
|
+
Object.assign(_pageData, newData);
|
|
191
|
+
}
|
|
192
|
+
function usePageData() {
|
|
193
|
+
const data = inject(DATA_KEY);
|
|
194
|
+
if (data === void 0) {
|
|
195
|
+
throw new Error("[vono] usePageData() must be called inside a component setup().");
|
|
196
|
+
}
|
|
197
|
+
return data;
|
|
198
|
+
}
|
|
199
|
+
async function boot(options) {
|
|
200
|
+
const container = document.getElementById("vono-app");
|
|
201
|
+
if (!container) {
|
|
202
|
+
console.error("[vono] #vono-app not found \u2014 aborting hydration.");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const { pages } = resolveRoutes(options.routes, {
|
|
206
|
+
...options.layout !== void 0 && { layout: options.layout },
|
|
207
|
+
middleware: []
|
|
208
|
+
});
|
|
209
|
+
const stateMap = window[STATE_KEY] ?? {};
|
|
210
|
+
const seoData = window[SEO_KEY] ?? {};
|
|
211
|
+
const pathname = location.pathname;
|
|
212
|
+
updatePageData(stateMap[pathname] ?? {});
|
|
213
|
+
syncSEO(seoData);
|
|
214
|
+
const vueRoutes = pages.map((r) => {
|
|
215
|
+
const layout = r.layout !== void 0 ? r.layout : options.layout;
|
|
216
|
+
const comp = r.page.component;
|
|
217
|
+
const PageComp = isAsyncLoader(comp) ? defineAsyncComponent(comp) : comp;
|
|
218
|
+
const routeComp = layout ? defineComponent({
|
|
219
|
+
name: "VonoRoute",
|
|
220
|
+
setup: () => () => h(layout.component, null, {
|
|
221
|
+
default: () => h(PageComp)
|
|
222
|
+
})
|
|
223
|
+
}) : PageComp;
|
|
224
|
+
return { path: toVueRouterPath(r.fullPath), component: routeComp };
|
|
225
|
+
});
|
|
226
|
+
const currentRoute = pages.find((r) => matchPath(compilePath(r.fullPath), pathname) !== null);
|
|
227
|
+
if (currentRoute && isAsyncLoader(currentRoute.page.component)) {
|
|
228
|
+
await currentRoute.page.component();
|
|
229
|
+
}
|
|
230
|
+
const app = createSSRApp({ name: "VonoApp", render: () => h(RouterView) });
|
|
231
|
+
app.provide(DATA_KEY, readonly(_pageData));
|
|
232
|
+
const router = createRouter({ history: createWebHistory(), routes: vueRoutes });
|
|
233
|
+
let isInitialNav = true;
|
|
234
|
+
router.beforeEach(async (to, _from, next) => {
|
|
235
|
+
if (isInitialNav) {
|
|
236
|
+
isInitialNav = false;
|
|
237
|
+
return next();
|
|
238
|
+
}
|
|
239
|
+
const href = new URL(to.fullPath, location.origin).toString();
|
|
240
|
+
try {
|
|
241
|
+
await runMw(to.fullPath, async () => {
|
|
242
|
+
const payload = await fetchSPA(href);
|
|
243
|
+
updatePageData(payload.state ?? {});
|
|
244
|
+
syncSEO(payload.seo ?? {});
|
|
245
|
+
window.scrollTo(0, 0);
|
|
246
|
+
});
|
|
247
|
+
next();
|
|
248
|
+
} catch (err) {
|
|
249
|
+
console.error("[vono] Navigation error:", err);
|
|
250
|
+
location.href = to.fullPath;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
app.use(router);
|
|
254
|
+
await router.isReady();
|
|
255
|
+
app.mount(container);
|
|
256
|
+
if (options.prefetchOnHover !== false) {
|
|
257
|
+
document.addEventListener("mouseover", (e) => {
|
|
258
|
+
const a = e.composedPath().find((el) => el instanceof HTMLAnchorElement);
|
|
259
|
+
if (a?.href) prefetch(a.href);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
export {
|
|
264
|
+
DATA_KEY,
|
|
265
|
+
PARAMS_KEY,
|
|
266
|
+
RouterLink,
|
|
267
|
+
RouterView2 as RouterView,
|
|
268
|
+
SEO_KEY,
|
|
269
|
+
SPA_HEADER,
|
|
270
|
+
STATE_KEY,
|
|
271
|
+
boot,
|
|
272
|
+
compilePath,
|
|
273
|
+
defineApiRoute,
|
|
274
|
+
defineGroup,
|
|
275
|
+
defineLayout,
|
|
276
|
+
definePage,
|
|
277
|
+
isAsyncLoader,
|
|
278
|
+
matchPath,
|
|
279
|
+
prefetch,
|
|
280
|
+
resolveRoutes,
|
|
281
|
+
syncSEO,
|
|
282
|
+
toVueRouterPath,
|
|
283
|
+
useClientMiddleware,
|
|
284
|
+
usePageData,
|
|
285
|
+
useRoute,
|
|
286
|
+
useRouter
|
|
287
|
+
};
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Component } from 'vue';
|
|
2
|
+
import { Hono, MiddlewareHandler, Context } from 'hono';
|
|
3
|
+
|
|
4
|
+
type HonoMiddleware = MiddlewareHandler;
|
|
5
|
+
type LoaderCtx = Context;
|
|
6
|
+
interface SEOMeta {
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
keywords?: string;
|
|
10
|
+
author?: string;
|
|
11
|
+
robots?: string;
|
|
12
|
+
canonical?: string;
|
|
13
|
+
themeColor?: string;
|
|
14
|
+
ogTitle?: string;
|
|
15
|
+
ogDescription?: string;
|
|
16
|
+
ogImage?: string;
|
|
17
|
+
ogImageAlt?: string;
|
|
18
|
+
ogUrl?: string;
|
|
19
|
+
ogType?: string;
|
|
20
|
+
ogSiteName?: string;
|
|
21
|
+
twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player';
|
|
22
|
+
twitterSite?: string;
|
|
23
|
+
twitterTitle?: string;
|
|
24
|
+
twitterDescription?: string;
|
|
25
|
+
twitterImage?: string;
|
|
26
|
+
/** Structured data injected as <script type="application/ld+json">. */
|
|
27
|
+
jsonLd?: Record<string, unknown> | Record<string, unknown>[];
|
|
28
|
+
}
|
|
29
|
+
type AsyncLoader = () => Promise<{
|
|
30
|
+
default: Component;
|
|
31
|
+
} | Component>;
|
|
32
|
+
interface PageDef<TData extends object = Record<string, never>> {
|
|
33
|
+
readonly __type: 'page';
|
|
34
|
+
path: string;
|
|
35
|
+
middleware?: HonoMiddleware[];
|
|
36
|
+
loader?: (c: LoaderCtx) => TData | Promise<TData>;
|
|
37
|
+
seo?: SEOMeta | ((data: TData, params: Record<string, string>) => SEOMeta);
|
|
38
|
+
/** Override or disable the app-level layout for this route. */
|
|
39
|
+
layout?: LayoutDef | false;
|
|
40
|
+
/**
|
|
41
|
+
* The Vue component to render for this route.
|
|
42
|
+
* Use () => import('./Page.vue') for automatic code splitting.
|
|
43
|
+
*/
|
|
44
|
+
component: Component | AsyncLoader;
|
|
45
|
+
}
|
|
46
|
+
interface GroupDef {
|
|
47
|
+
readonly __type: 'group';
|
|
48
|
+
prefix: string;
|
|
49
|
+
layout?: LayoutDef | false;
|
|
50
|
+
middleware?: HonoMiddleware[];
|
|
51
|
+
routes: Route[];
|
|
52
|
+
}
|
|
53
|
+
interface LayoutDef {
|
|
54
|
+
readonly __type: 'layout';
|
|
55
|
+
/** Vue layout component — must contain <slot /> for page content. */
|
|
56
|
+
component: Component;
|
|
57
|
+
}
|
|
58
|
+
interface ApiRouteDef {
|
|
59
|
+
readonly __type: 'api';
|
|
60
|
+
path: string;
|
|
61
|
+
register: (app: Hono, globalMiddleware: HonoMiddleware[]) => void;
|
|
62
|
+
}
|
|
63
|
+
type Route = PageDef<any> | GroupDef | ApiRouteDef;
|
|
64
|
+
interface AppConfig {
|
|
65
|
+
layout?: LayoutDef;
|
|
66
|
+
seo?: SEOMeta;
|
|
67
|
+
middleware?: HonoMiddleware[];
|
|
68
|
+
routes: Route[];
|
|
69
|
+
notFound?: Component;
|
|
70
|
+
htmlAttrs?: Record<string, string>;
|
|
71
|
+
/** Extra HTML injected into <head> (e.g. font preloads). */
|
|
72
|
+
head?: string;
|
|
73
|
+
}
|
|
74
|
+
interface ResolvedRoute {
|
|
75
|
+
fullPath: string;
|
|
76
|
+
page: PageDef<any>;
|
|
77
|
+
layout: LayoutDef | false | undefined;
|
|
78
|
+
middleware: HonoMiddleware[];
|
|
79
|
+
}
|
|
80
|
+
interface CompiledPath {
|
|
81
|
+
re: RegExp;
|
|
82
|
+
keys: string[];
|
|
83
|
+
}
|
|
84
|
+
type ClientMiddleware = (url: string, next: () => Promise<void>) => Promise<void>;
|
|
85
|
+
/** Custom request header that identifies an SPA navigation (JSON payload). */
|
|
86
|
+
declare const SPA_HEADER = "x-vono-spa";
|
|
87
|
+
/** window key for SSR-injected per-page loader data. */
|
|
88
|
+
declare const STATE_KEY = "__VONO_STATE__";
|
|
89
|
+
/** window key for SSR-injected URL params. */
|
|
90
|
+
declare const PARAMS_KEY = "__VONO_PARAMS__";
|
|
91
|
+
/** window key for SSR-injected SEO meta. */
|
|
92
|
+
declare const SEO_KEY = "__VONO_SEO__";
|
|
93
|
+
/**
|
|
94
|
+
* Vue provide/inject key for the reactive page-data object.
|
|
95
|
+
* Symbol.for() ensures the same reference across module instances (SSR safe).
|
|
96
|
+
*/
|
|
97
|
+
declare const DATA_KEY: unique symbol;
|
|
98
|
+
/**
|
|
99
|
+
* Extract the loader data type from a `PageDef` returned by `definePage()`.
|
|
100
|
+
*
|
|
101
|
+
* This enables you to define the data type exactly once — inferred from the
|
|
102
|
+
* loader — and import it into page components for `usePageData<T>()`.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* // app/routes.ts
|
|
106
|
+
* export const homePage = definePage({
|
|
107
|
+
* path: '/',
|
|
108
|
+
* loader: async () => ({ title: 'Hello', count: 42 }),
|
|
109
|
+
* component: () => import('./pages/home.vue'),
|
|
110
|
+
* })
|
|
111
|
+
* export type HomeData = InferPageData<typeof homePage>
|
|
112
|
+
* // HomeData = { title: string; count: number }
|
|
113
|
+
*
|
|
114
|
+
* // app/pages/home.vue
|
|
115
|
+
* import type { HomeData } from '../routes'
|
|
116
|
+
* const data = usePageData<HomeData>()
|
|
117
|
+
*/
|
|
118
|
+
type InferPageData<T> = T extends PageDef<infer D> ? D : never;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Returns true when `c` is an async factory function (i.e. `() => import(...)`)
|
|
122
|
+
* rather than a resolved Vue component object.
|
|
123
|
+
*
|
|
124
|
+
* Used by both server.ts (to resolve the import before SSR) and client.ts
|
|
125
|
+
* (to wrap with defineAsyncComponent for lazy hydration).
|
|
126
|
+
*/
|
|
127
|
+
declare function isAsyncLoader(c: unknown): c is AsyncLoader;
|
|
128
|
+
/**
|
|
129
|
+
* Define a page route with full type inference.
|
|
130
|
+
*
|
|
131
|
+
* TypeScript infers `TData` automatically from the `loader` return type, so
|
|
132
|
+
* you rarely need to supply the generic manually. Export the page constant and
|
|
133
|
+
* use `InferPageData<typeof myPage>` in your component for a single source of
|
|
134
|
+
* truth.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* export const postPage = definePage({
|
|
138
|
+
* path: '/post/[slug]',
|
|
139
|
+
* loader: async (c) => fetchPost(c.req.param('slug')),
|
|
140
|
+
* component: () => import('./pages/post.vue'),
|
|
141
|
+
* })
|
|
142
|
+
* export type PostData = InferPageData<typeof postPage>
|
|
143
|
+
*/
|
|
144
|
+
declare function definePage<TData extends object = Record<string, never>>(def: Omit<PageDef<TData>, '__type'>): PageDef<TData>;
|
|
145
|
+
declare function defineGroup(def: Omit<GroupDef, '__type'>): GroupDef;
|
|
146
|
+
/** Wrap a Vue layout component (must render <slot />) as a Vono layout. */
|
|
147
|
+
declare function defineLayout(component: Component): LayoutDef;
|
|
148
|
+
declare function defineApiRoute(path: string, register: ApiRouteDef['register']): ApiRouteDef;
|
|
149
|
+
declare function compilePath(path: string): CompiledPath;
|
|
150
|
+
declare function matchPath(cp: CompiledPath, pathname: string): Record<string, string> | null;
|
|
151
|
+
/**
|
|
152
|
+
* Convert Vono `[param]` syntax to Vue Router `:param` syntax.
|
|
153
|
+
*
|
|
154
|
+
* `/posts/[slug]` → `/posts/:slug`
|
|
155
|
+
* `/files/[...path]` → `/files/:path(.*)*`
|
|
156
|
+
*/
|
|
157
|
+
declare function toVueRouterPath(vonoPath: string): string;
|
|
158
|
+
declare function resolveRoutes(routes: Route[], options?: {
|
|
159
|
+
prefix?: string;
|
|
160
|
+
middleware?: HonoMiddleware[];
|
|
161
|
+
layout?: LayoutDef | false;
|
|
162
|
+
}): {
|
|
163
|
+
pages: ResolvedRoute[];
|
|
164
|
+
apis: ApiRouteDef[];
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export { type ApiRouteDef, type AppConfig, type AsyncLoader, type ClientMiddleware, type CompiledPath, DATA_KEY, type GroupDef, type HonoMiddleware, type InferPageData, type LayoutDef, type LoaderCtx, PARAMS_KEY, type PageDef, type ResolvedRoute, type Route, type SEOMeta, SEO_KEY, SPA_HEADER, STATE_KEY, compilePath, defineApiRoute, defineGroup, defineLayout, definePage, isAsyncLoader, matchPath, resolveRoutes, toVueRouterPath };
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// types.ts
|
|
2
|
+
var SPA_HEADER = "x-vono-spa";
|
|
3
|
+
var STATE_KEY = "__VONO_STATE__";
|
|
4
|
+
var PARAMS_KEY = "__VONO_PARAMS__";
|
|
5
|
+
var SEO_KEY = "__VONO_SEO__";
|
|
6
|
+
var DATA_KEY = /* @__PURE__ */ Symbol.for("vono:data");
|
|
7
|
+
|
|
8
|
+
// core.ts
|
|
9
|
+
var VUE_BRANDS = ["__name", "__file", "__vccOpts", "setup", "render", "data", "components"];
|
|
10
|
+
function isAsyncLoader(c) {
|
|
11
|
+
if (typeof c !== "function") return false;
|
|
12
|
+
const f = c;
|
|
13
|
+
for (const brand of VUE_BRANDS) {
|
|
14
|
+
if (brand in f) return false;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
function definePage(def) {
|
|
19
|
+
return { __type: "page", ...def };
|
|
20
|
+
}
|
|
21
|
+
function defineGroup(def) {
|
|
22
|
+
return { __type: "group", ...def };
|
|
23
|
+
}
|
|
24
|
+
function defineLayout(component) {
|
|
25
|
+
return { __type: "layout", component };
|
|
26
|
+
}
|
|
27
|
+
function defineApiRoute(path, register) {
|
|
28
|
+
return { __type: "api", path, register };
|
|
29
|
+
}
|
|
30
|
+
function compilePath(path) {
|
|
31
|
+
const keys = [];
|
|
32
|
+
const src = path.replace(/\[\.\.\.([^\]]+)\]/g, (_, k) => {
|
|
33
|
+
keys.push(k);
|
|
34
|
+
return "(.*)";
|
|
35
|
+
}).replace(/\[([^\]]+)\]/g, (_, k) => {
|
|
36
|
+
keys.push(k);
|
|
37
|
+
return "([^/]+)";
|
|
38
|
+
}).replace(/\*/g, "(.*)");
|
|
39
|
+
return { re: new RegExp(`^${src}$`), keys };
|
|
40
|
+
}
|
|
41
|
+
function matchPath(cp, pathname) {
|
|
42
|
+
const m = pathname.match(cp.re);
|
|
43
|
+
if (!m) return null;
|
|
44
|
+
const params = {};
|
|
45
|
+
cp.keys.forEach((k, i) => {
|
|
46
|
+
params[k] = decodeURIComponent(m[i + 1] ?? "");
|
|
47
|
+
});
|
|
48
|
+
return params;
|
|
49
|
+
}
|
|
50
|
+
function toVueRouterPath(vonoPath) {
|
|
51
|
+
return vonoPath.replace(/\[\.\.\.([^\]]+)\]/g, ":$1(.*)*").replace(/\[([^\]]+)\]/g, ":$1");
|
|
52
|
+
}
|
|
53
|
+
function resolveRoutes(routes, options = {}) {
|
|
54
|
+
const pages = [];
|
|
55
|
+
const apis = [];
|
|
56
|
+
for (const route of routes) {
|
|
57
|
+
if (route.__type === "api") {
|
|
58
|
+
apis.push({ ...route, path: (options.prefix ?? "") + route.path });
|
|
59
|
+
} else if (route.__type === "group") {
|
|
60
|
+
const prefix = (options.prefix ?? "") + route.prefix;
|
|
61
|
+
const mw = [...options.middleware ?? [], ...route.middleware ?? []];
|
|
62
|
+
const layout = route.layout !== void 0 ? route.layout : options.layout;
|
|
63
|
+
const sub = resolveRoutes(route.routes, {
|
|
64
|
+
prefix,
|
|
65
|
+
middleware: mw,
|
|
66
|
+
...layout !== void 0 && { layout }
|
|
67
|
+
});
|
|
68
|
+
pages.push(...sub.pages);
|
|
69
|
+
apis.push(...sub.apis);
|
|
70
|
+
} else {
|
|
71
|
+
pages.push({
|
|
72
|
+
fullPath: (options.prefix ?? "") + route.path,
|
|
73
|
+
page: route,
|
|
74
|
+
layout: route.layout !== void 0 ? route.layout : options.layout,
|
|
75
|
+
middleware: [...options.middleware ?? [], ...route.middleware ?? []]
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { pages, apis };
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
DATA_KEY,
|
|
83
|
+
PARAMS_KEY,
|
|
84
|
+
SEO_KEY,
|
|
85
|
+
SPA_HEADER,
|
|
86
|
+
STATE_KEY,
|
|
87
|
+
compilePath,
|
|
88
|
+
defineApiRoute,
|
|
89
|
+
defineGroup,
|
|
90
|
+
defineLayout,
|
|
91
|
+
definePage,
|
|
92
|
+
isAsyncLoader,
|
|
93
|
+
matchPath,
|
|
94
|
+
resolveRoutes,
|
|
95
|
+
toVueRouterPath
|
|
96
|
+
};
|