@netrojs/fnetro 0.2.20 → 0.3.0

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/client.js CHANGED
@@ -1,20 +1,71 @@
1
1
  // client.ts
2
- import { createSignal, createMemo, createComponent } from "solid-js";
3
- import { hydrate } from "solid-js/web";
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-fnetro-spa";
19
+ var STATE_KEY = "__FNETRO_STATE__";
20
+ var PARAMS_KEY = "__FNETRO_PARAMS__";
21
+ var SEO_KEY = "__FNETRO_SEO__";
22
+ var DATA_KEY = /* @__PURE__ */ Symbol.for("fnetro:data");
4
23
 
5
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
+ }
6
34
  function definePage(def) {
7
35
  return { __type: "page", ...def };
8
36
  }
9
37
  function defineGroup(def) {
10
38
  return { __type: "group", ...def };
11
39
  }
12
- function defineLayout(Component) {
13
- return { __type: "layout", Component };
40
+ function defineLayout(component) {
41
+ return { __type: "layout", component };
14
42
  }
15
43
  function defineApiRoute(path, register) {
16
44
  return { __type: "api", path, register };
17
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(fnetroPath) {
67
+ return fnetroPath.replace(/\[\.\.\.([^\]]+)\]/g, ":$1(.*)*").replace(/\[([^\]]+)\]/g, ":$1");
68
+ }
18
69
  function resolveRoutes(routes, options = {}) {
19
70
  const pages = [];
20
71
  const apis = [];
@@ -25,7 +76,11 @@ function resolveRoutes(routes, options = {}) {
25
76
  const prefix = (options.prefix ?? "") + route.prefix;
26
77
  const mw = [...options.middleware ?? [], ...route.middleware ?? []];
27
78
  const layout = route.layout !== void 0 ? route.layout : options.layout;
28
- const sub = resolveRoutes(route.routes, { prefix, middleware: mw, layout });
79
+ const sub = resolveRoutes(route.routes, {
80
+ prefix,
81
+ middleware: mw,
82
+ ...layout !== void 0 && { layout }
83
+ });
29
84
  pages.push(...sub.pages);
30
85
  apis.push(...sub.apis);
31
86
  } else {
@@ -39,58 +94,14 @@ function resolveRoutes(routes, options = {}) {
39
94
  }
40
95
  return { pages, apis };
41
96
  }
42
- function compilePath(path) {
43
- const keys = [];
44
- const src = path.replace(/\[\.\.\.([^\]]+)\]/g, (_, k) => {
45
- keys.push(k);
46
- return "(.*)";
47
- }).replace(/\[([^\]]+)\]/g, (_, k) => {
48
- keys.push(k);
49
- return "([^/]+)";
50
- }).replace(/\*/g, "(.*)");
51
- return { re: new RegExp(`^${src}$`), keys };
52
- }
53
- function matchPath(compiled, pathname) {
54
- const m = pathname.match(compiled.re);
55
- if (!m) return null;
56
- const params = {};
57
- compiled.keys.forEach((k, i) => {
58
- params[k] = decodeURIComponent(m[i + 1] ?? "");
59
- });
60
- return params;
61
- }
62
- var SPA_HEADER = "x-fnetro-spa";
63
- var STATE_KEY = "__FNETRO_STATE__";
64
- var PARAMS_KEY = "__FNETRO_PARAMS__";
65
- var SEO_KEY = "__FNETRO_SEO__";
66
97
 
67
98
  // client.ts
68
- var _routes = [];
69
- var _appLayout;
70
- function findRoute(pathname) {
71
- for (const { route, cp } of _routes) {
72
- const params = matchPath(cp, pathname);
73
- if (params !== null) return { route, params };
74
- }
75
- return null;
76
- }
77
- var _setNav = null;
78
- var _mw = [];
79
- function useClientMiddleware(mw) {
80
- _mw.push(mw);
81
- }
82
- async function runMiddleware(url, done) {
83
- const chain = [..._mw, async (_u, next) => {
84
- await done();
85
- await next();
86
- }];
87
- let i = 0;
88
- const run = async () => {
89
- const fn = chain[i++];
90
- if (fn) await fn(url, run);
91
- };
92
- await run();
93
- }
99
+ import {
100
+ useRoute,
101
+ useRouter,
102
+ RouterLink,
103
+ RouterView as RouterView2
104
+ } from "vue-router";
94
105
  function setMeta(selector, attr, val) {
95
106
  if (!val) {
96
107
  document.querySelector(selector)?.remove();
@@ -99,8 +110,8 @@ function setMeta(selector, attr, val) {
99
110
  let el = document.querySelector(selector);
100
111
  if (!el) {
101
112
  el = document.createElement("meta");
102
- const m = /\[(\w+[:-]?\w*)="([^"]+)"\]/.exec(selector);
103
- if (m) el.setAttribute(m[1], m[2]);
113
+ const [, attrName = "", attrVal = ""] = /\[([^=]+)="([^"]+)"\]/.exec(selector) ?? [];
114
+ if (attrName) el.setAttribute(attrName, attrVal);
104
115
  document.head.appendChild(el);
105
116
  }
106
117
  el.setAttribute(attr, val);
@@ -120,149 +131,140 @@ function syncSEO(seo) {
120
131
  setMeta('[name="twitter:title"]', "content", seo.twitterTitle);
121
132
  setMeta('[name="twitter:description"]', "content", seo.twitterDescription);
122
133
  setMeta('[name="twitter:image"]', "content", seo.twitterImage);
123
- const canon = seo.canonical;
124
- let linkEl = document.querySelector('link[rel="canonical"]');
125
- if (canon) {
126
- if (!linkEl) {
127
- linkEl = document.createElement("link");
128
- linkEl.rel = "canonical";
129
- document.head.appendChild(linkEl);
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);
130
140
  }
131
- linkEl.href = canon;
141
+ link.href = seo.canonical;
132
142
  } else {
133
- linkEl?.remove();
143
+ link?.remove();
134
144
  }
135
145
  }
136
- var _cache = /* @__PURE__ */ new Map();
137
- function fetchPayload(href) {
138
- if (!_cache.has(href)) {
139
- _cache.set(
146
+ var _fetchCache = /* @__PURE__ */ new Map();
147
+ function fetchSPA(href) {
148
+ if (!_fetchCache.has(href)) {
149
+ _fetchCache.set(
140
150
  href,
141
151
  fetch(href, { headers: { [SPA_HEADER]: "1" } }).then((r) => {
142
- if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
152
+ if (!r.ok) throw new Error(`[fnetro] ${r.status} ${r.statusText} \u2014 ${href}`);
143
153
  return r.json();
144
154
  })
145
155
  );
146
156
  }
147
- return _cache.get(href);
148
- }
149
- async function navigate(to, opts = {}) {
150
- const u = new URL(to, location.origin);
151
- if (u.origin !== location.origin) {
152
- location.href = to;
153
- return;
154
- }
155
- if (!findRoute(u.pathname)) {
156
- location.href = to;
157
- return;
158
- }
159
- await runMiddleware(u.pathname, async () => {
160
- try {
161
- const payload = await fetchPayload(u.toString());
162
- history[opts.replace ? "replaceState" : "pushState"](
163
- { url: u.pathname },
164
- "",
165
- u.pathname
166
- );
167
- if (opts.scroll !== false) window.scrollTo(0, 0);
168
- _setNav?.({ path: u.pathname, data: payload.state ?? {}, params: payload.params ?? {} });
169
- syncSEO(payload.seo ?? {});
170
- } catch (err) {
171
- console.error("[fnetro] Navigation error:", err);
172
- location.href = to;
173
- }
174
- });
157
+ return _fetchCache.get(href);
175
158
  }
176
159
  function prefetch(url) {
177
160
  try {
178
161
  const u = new URL(url, location.origin);
179
- if (u.origin !== location.origin || !findRoute(u.pathname)) return;
180
- fetchPayload(u.toString());
162
+ if (u.origin === location.origin) fetchSPA(u.toString());
181
163
  } catch {
182
164
  }
183
165
  }
184
- function onLinkClick(e) {
185
- if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
186
- const a = e.composedPath().find(
187
- (el) => el instanceof HTMLAnchorElement
188
- );
189
- if (!a?.href) return;
190
- if (a.target && a.target !== "_self") return;
191
- if (a.hasAttribute("data-no-spa") || a.rel?.includes("external")) return;
192
- const u = new URL(a.href);
193
- if (u.origin !== location.origin) return;
194
- e.preventDefault();
195
- navigate(a.href);
166
+ var _mw = [];
167
+ function useClientMiddleware(mw) {
168
+ _mw.push(mw);
196
169
  }
197
- function onLinkHover(e) {
198
- const a = e.composedPath().find(
199
- (el) => el instanceof HTMLAnchorElement
200
- );
201
- if (a?.href) prefetch(a.href);
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();
202
184
  }
203
- function onPopState() {
204
- navigate(location.href, { replace: true, scroll: false });
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);
205
191
  }
206
- function AppRoot(props) {
207
- const [nav, setNav] = createSignal(props.initial);
208
- _setNav = setNav;
209
- const view = createMemo(() => {
210
- const { path, data, params } = nav();
211
- const m = findRoute(path);
212
- if (!m) {
213
- return null;
214
- }
215
- const layout = m.route.layout !== void 0 ? m.route.layout : props.appLayout;
216
- const pageEl = createComponent(m.route.page.Page, { ...data, url: path, params });
217
- if (!layout) return pageEl;
218
- return createComponent(layout.Component, {
219
- url: path,
220
- params,
221
- get children() {
222
- return pageEl;
223
- }
224
- });
225
- });
226
- return view;
192
+ function usePageData() {
193
+ const data = inject(DATA_KEY);
194
+ if (data === void 0) {
195
+ throw new Error("[fnetro] usePageData() must be called inside a component setup().");
196
+ }
197
+ return data;
227
198
  }
228
199
  async function boot(options) {
200
+ const container = document.getElementById("fnetro-app");
201
+ if (!container) {
202
+ console.error("[fnetro] #fnetro-app not found \u2014 aborting hydration.");
203
+ return;
204
+ }
229
205
  const { pages } = resolveRoutes(options.routes, {
230
- layout: options.layout,
206
+ ...options.layout !== void 0 && { layout: options.layout },
231
207
  middleware: []
232
208
  });
233
- _routes = pages.map((r) => ({ route: r, cp: compilePath(r.fullPath) }));
234
- _appLayout = options.layout;
235
- const pathname = location.pathname;
236
- if (!findRoute(pathname)) {
237
- console.warn(`[fnetro] No route matched "${pathname}" \u2014 skipping hydration`);
238
- return;
239
- }
240
209
  const stateMap = window[STATE_KEY] ?? {};
241
- const paramsMap = window[PARAMS_KEY] ?? {};
242
210
  const seoData = window[SEO_KEY] ?? {};
243
- const initial = {
244
- path: pathname,
245
- data: stateMap[pathname] ?? {},
246
- params: paramsMap
247
- };
248
- const container = document.getElementById("fnetro-app");
249
- if (!container) {
250
- console.error("[fnetro] #fnetro-app not found \u2014 aborting hydration");
251
- return;
252
- }
211
+ const pathname = location.pathname;
212
+ updatePageData(stateMap[pathname] ?? {});
253
213
  syncSEO(seoData);
254
- hydrate(
255
- () => createComponent(AppRoot, { initial, appLayout: _appLayout }),
256
- container
257
- );
258
- document.addEventListener("click", onLinkClick);
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: "FNetroRoute",
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: "FNetroApp", 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("[fnetro] Navigation error:", err);
250
+ location.href = to.fullPath;
251
+ }
252
+ });
253
+ app.use(router);
254
+ await router.isReady();
255
+ app.mount(container);
259
256
  if (options.prefetchOnHover !== false) {
260
- document.addEventListener("mouseover", onLinkHover);
257
+ document.addEventListener("mouseover", (e) => {
258
+ const a = e.composedPath().find((el) => el instanceof HTMLAnchorElement);
259
+ if (a?.href) prefetch(a.href);
260
+ });
261
261
  }
262
- window.addEventListener("popstate", onPopState);
263
262
  }
264
263
  export {
264
+ DATA_KEY,
265
265
  PARAMS_KEY,
266
+ RouterLink,
267
+ RouterView2 as RouterView,
266
268
  SEO_KEY,
267
269
  SPA_HEADER,
268
270
  STATE_KEY,
@@ -272,9 +274,14 @@ export {
272
274
  defineGroup,
273
275
  defineLayout,
274
276
  definePage,
277
+ isAsyncLoader,
275
278
  matchPath,
276
- navigate,
277
279
  prefetch,
278
280
  resolveRoutes,
279
- useClientMiddleware
281
+ syncSEO,
282
+ toVueRouterPath,
283
+ useClientMiddleware,
284
+ usePageData,
285
+ useRoute,
286
+ useRouter
280
287
  };
package/dist/core.d.ts CHANGED
@@ -1,5 +1,5 @@
1
+ import { Component } from 'vue';
1
2
  import { Hono, MiddlewareHandler, Context } from 'hono';
2
- import { Component, JSX } from 'solid-js';
3
3
 
4
4
  type HonoMiddleware = MiddlewareHandler;
5
5
  type LoaderCtx = Context;
@@ -15,44 +15,33 @@ interface SEOMeta {
15
15
  ogDescription?: string;
16
16
  ogImage?: string;
17
17
  ogImageAlt?: string;
18
- ogImageWidth?: string;
19
- ogImageHeight?: string;
20
18
  ogUrl?: string;
21
19
  ogType?: string;
22
20
  ogSiteName?: string;
23
- ogLocale?: string;
24
21
  twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player';
25
22
  twitterSite?: string;
26
- twitterCreator?: string;
27
23
  twitterTitle?: string;
28
24
  twitterDescription?: string;
29
25
  twitterImage?: string;
30
- twitterImageAlt?: string;
26
+ /** Structured data injected as <script type="application/ld+json">. */
31
27
  jsonLd?: Record<string, unknown> | Record<string, unknown>[];
32
- extra?: Array<{
33
- name?: string;
34
- property?: string;
35
- httpEquiv?: string;
36
- content: string;
37
- }>;
38
28
  }
39
- type PageProps<TData extends object = {}> = TData & {
40
- url: string;
41
- params: Record<string, string>;
42
- };
43
- interface LayoutProps {
44
- children: JSX.Element;
45
- url: string;
46
- params: Record<string, string>;
47
- }
48
- interface PageDef<TData extends object = {}> {
29
+ type AsyncLoader = () => Promise<{
30
+ default: Component;
31
+ } | Component>;
32
+ interface PageDef<TData extends object = Record<string, never>> {
49
33
  readonly __type: 'page';
50
34
  path: string;
51
35
  middleware?: HonoMiddleware[];
52
36
  loader?: (c: LoaderCtx) => TData | Promise<TData>;
53
37
  seo?: SEOMeta | ((data: TData, params: Record<string, string>) => SEOMeta);
38
+ /** Override or disable the app-level layout for this route. */
54
39
  layout?: LayoutDef | false;
55
- Page: Component<PageProps<TData>>;
40
+ /**
41
+ * The Vue component to render for this route.
42
+ * Use () => import('./Page.vue') for automatic code splitting.
43
+ */
44
+ component: Component | AsyncLoader;
56
45
  }
57
46
  interface GroupDef {
58
47
  readonly __type: 'group';
@@ -63,7 +52,8 @@ interface GroupDef {
63
52
  }
64
53
  interface LayoutDef {
65
54
  readonly __type: 'layout';
66
- Component: Component<LayoutProps>;
55
+ /** Vue layout component — must contain <slot /> for page content. */
56
+ component: Component;
67
57
  }
68
58
  interface ApiRouteDef {
69
59
  readonly __type: 'api';
@@ -78,36 +68,63 @@ interface AppConfig {
78
68
  routes: Route[];
79
69
  notFound?: Component;
80
70
  htmlAttrs?: Record<string, string>;
71
+ /** Extra HTML injected into <head> (e.g. font preloads). */
81
72
  head?: string;
82
73
  }
83
- type ClientMiddleware = (url: string, next: () => Promise<void>) => Promise<void>;
84
- declare function definePage<TData extends object = {}>(def: Omit<PageDef<TData>, '__type'>): PageDef<TData>;
85
- declare function defineGroup(def: Omit<GroupDef, '__type'>): GroupDef;
86
- declare function defineLayout(Component: Component<LayoutProps>): LayoutDef;
87
- declare function defineApiRoute(path: string, register: ApiRouteDef['register']): ApiRouteDef;
88
74
  interface ResolvedRoute {
89
75
  fullPath: string;
90
76
  page: PageDef<any>;
91
77
  layout: LayoutDef | false | undefined;
92
78
  middleware: HonoMiddleware[];
93
79
  }
94
- declare function resolveRoutes(routes: Route[], options?: {
95
- prefix?: string;
96
- middleware?: HonoMiddleware[];
97
- layout?: LayoutDef | false;
98
- }): {
99
- pages: ResolvedRoute[];
100
- apis: ApiRouteDef[];
101
- };
102
80
  interface CompiledPath {
103
81
  re: RegExp;
104
82
  keys: string[];
105
83
  }
106
- declare function compilePath(path: string): CompiledPath;
107
- declare function matchPath(compiled: CompiledPath, pathname: string): Record<string, string> | null;
84
+ type ClientMiddleware = (url: string, next: () => Promise<void>) => Promise<void>;
85
+ /** Custom request header that identifies an SPA navigation (JSON payload). */
108
86
  declare const SPA_HEADER = "x-fnetro-spa";
87
+ /** window key for SSR-injected per-page loader data. */
109
88
  declare const STATE_KEY = "__FNETRO_STATE__";
89
+ /** window key for SSR-injected URL params. */
110
90
  declare const PARAMS_KEY = "__FNETRO_PARAMS__";
91
+ /** window key for SSR-injected SEO meta. */
111
92
  declare const SEO_KEY = "__FNETRO_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
+ /**
100
+ * Returns true when `c` is an async factory function (i.e. `() => import(...)`)
101
+ * rather than a resolved Vue component object.
102
+ *
103
+ * Used by both server.ts (to resolve the import before SSR) and client.ts
104
+ * (to wrap with defineAsyncComponent for lazy hydration).
105
+ */
106
+ declare function isAsyncLoader(c: unknown): c is AsyncLoader;
107
+ declare function definePage<TData extends object = Record<string, never>>(def: Omit<PageDef<TData>, '__type'>): PageDef<TData>;
108
+ declare function defineGroup(def: Omit<GroupDef, '__type'>): GroupDef;
109
+ /** Wrap a Vue layout component (must render <slot />) as a FNetro layout. */
110
+ declare function defineLayout(component: Component): LayoutDef;
111
+ declare function defineApiRoute(path: string, register: ApiRouteDef['register']): ApiRouteDef;
112
+ declare function compilePath(path: string): CompiledPath;
113
+ declare function matchPath(cp: CompiledPath, pathname: string): Record<string, string> | null;
114
+ /**
115
+ * Convert FNetro `[param]` syntax to Vue Router `:param` syntax.
116
+ *
117
+ * `/posts/[slug]` → `/posts/:slug`
118
+ * `/files/[...path]` → `/files/:path(.*)*`
119
+ */
120
+ declare function toVueRouterPath(fnetroPath: string): string;
121
+ declare function resolveRoutes(routes: Route[], options?: {
122
+ prefix?: string;
123
+ middleware?: HonoMiddleware[];
124
+ layout?: LayoutDef | false;
125
+ }): {
126
+ pages: ResolvedRoute[];
127
+ apis: ApiRouteDef[];
128
+ };
112
129
 
113
- export { type ApiRouteDef, type AppConfig, type ClientMiddleware, type CompiledPath, type GroupDef, type HonoMiddleware, type LayoutDef, type LayoutProps, type LoaderCtx, PARAMS_KEY, type PageDef, type PageProps, type ResolvedRoute, type Route, type SEOMeta, SEO_KEY, SPA_HEADER, STATE_KEY, compilePath, defineApiRoute, defineGroup, defineLayout, definePage, matchPath, resolveRoutes };
130
+ export { type ApiRouteDef, type AppConfig, type AsyncLoader, type ClientMiddleware, type CompiledPath, DATA_KEY, type GroupDef, type HonoMiddleware, 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 CHANGED
@@ -1,16 +1,55 @@
1
+ // types.ts
2
+ var SPA_HEADER = "x-fnetro-spa";
3
+ var STATE_KEY = "__FNETRO_STATE__";
4
+ var PARAMS_KEY = "__FNETRO_PARAMS__";
5
+ var SEO_KEY = "__FNETRO_SEO__";
6
+ var DATA_KEY = /* @__PURE__ */ Symbol.for("fnetro:data");
7
+
1
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
+ }
2
18
  function definePage(def) {
3
19
  return { __type: "page", ...def };
4
20
  }
5
21
  function defineGroup(def) {
6
22
  return { __type: "group", ...def };
7
23
  }
8
- function defineLayout(Component) {
9
- return { __type: "layout", Component };
24
+ function defineLayout(component) {
25
+ return { __type: "layout", component };
10
26
  }
11
27
  function defineApiRoute(path, register) {
12
28
  return { __type: "api", path, register };
13
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(fnetroPath) {
51
+ return fnetroPath.replace(/\[\.\.\.([^\]]+)\]/g, ":$1(.*)*").replace(/\[([^\]]+)\]/g, ":$1");
52
+ }
14
53
  function resolveRoutes(routes, options = {}) {
15
54
  const pages = [];
16
55
  const apis = [];
@@ -21,7 +60,11 @@ function resolveRoutes(routes, options = {}) {
21
60
  const prefix = (options.prefix ?? "") + route.prefix;
22
61
  const mw = [...options.middleware ?? [], ...route.middleware ?? []];
23
62
  const layout = route.layout !== void 0 ? route.layout : options.layout;
24
- const sub = resolveRoutes(route.routes, { prefix, middleware: mw, layout });
63
+ const sub = resolveRoutes(route.routes, {
64
+ prefix,
65
+ middleware: mw,
66
+ ...layout !== void 0 && { layout }
67
+ });
25
68
  pages.push(...sub.pages);
26
69
  apis.push(...sub.apis);
27
70
  } else {
@@ -35,31 +78,8 @@ function resolveRoutes(routes, options = {}) {
35
78
  }
36
79
  return { pages, apis };
37
80
  }
38
- function compilePath(path) {
39
- const keys = [];
40
- const src = path.replace(/\[\.\.\.([^\]]+)\]/g, (_, k) => {
41
- keys.push(k);
42
- return "(.*)";
43
- }).replace(/\[([^\]]+)\]/g, (_, k) => {
44
- keys.push(k);
45
- return "([^/]+)";
46
- }).replace(/\*/g, "(.*)");
47
- return { re: new RegExp(`^${src}$`), keys };
48
- }
49
- function matchPath(compiled, pathname) {
50
- const m = pathname.match(compiled.re);
51
- if (!m) return null;
52
- const params = {};
53
- compiled.keys.forEach((k, i) => {
54
- params[k] = decodeURIComponent(m[i + 1] ?? "");
55
- });
56
- return params;
57
- }
58
- var SPA_HEADER = "x-fnetro-spa";
59
- var STATE_KEY = "__FNETRO_STATE__";
60
- var PARAMS_KEY = "__FNETRO_PARAMS__";
61
- var SEO_KEY = "__FNETRO_SEO__";
62
81
  export {
82
+ DATA_KEY,
63
83
  PARAMS_KEY,
64
84
  SEO_KEY,
65
85
  SPA_HEADER,
@@ -69,6 +89,8 @@ export {
69
89
  defineGroup,
70
90
  defineLayout,
71
91
  definePage,
92
+ isAsyncLoader,
72
93
  matchPath,
73
- resolveRoutes
94
+ resolveRoutes,
95
+ toVueRouterPath
74
96
  };