@moostjs/vite 0.6.26 → 0.6.27

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.cjs CHANGED
@@ -42,6 +42,31 @@ function entryBasename(entry) {
42
42
  return entry.split("/").pop().replace(/\.ts$/, ".js");
43
43
  }
44
44
  /**
45
+ * Normalizes the `prefix` option (single mount or list of mounts) into a list of
46
+ * normalized entries: leading slash ensured, trailing slash stripped.
47
+ * Returns `undefined` when no prefix is configured (empty input included) —
48
+ * the "route everything through Moost first" mode.
49
+ */ function normalizePrefixes(prefix) {
50
+ if (!prefix) return;
51
+ const normalized = (Array.isArray(prefix) ? prefix : [prefix]).filter(Boolean).map((entry) => {
52
+ let value = entry;
53
+ if (!value.startsWith("/")) value = `/${value}`;
54
+ if (value.endsWith("/")) value = value.slice(0, -1);
55
+ return value;
56
+ });
57
+ return normalized.length > 0 ? normalized : void 0;
58
+ }
59
+ /**
60
+ * Checks whether a request url falls under any of the normalized prefix mounts:
61
+ * exact match, a path segment below it, or the mount itself with a query string.
62
+ */ function matchesPrefix(url, prefixes) {
63
+ return prefixes.some((prefix) => {
64
+ if (!url.startsWith(prefix)) return false;
65
+ const next = url[prefix.length];
66
+ return next === void 0 || next === "/" || next === "?";
67
+ });
68
+ }
69
+ /**
45
70
  * Recursively gathers all importer modules upstream from a given module.
46
71
  *
47
72
  * @param {EnvironmentModuleNode | undefined} moduleNode - The starting module node.
@@ -340,13 +365,7 @@ function moostVite(options) {
340
365
  const isTest = process.env.NODE_ENV === "test";
341
366
  const isProd = process.env.NODE_ENV === "production";
342
367
  const externals = options.externals ?? true;
343
- let prefix = options.prefix;
344
- if (prefix) {
345
- if (!prefix.startsWith("/")) prefix = `/${prefix}`;
346
- if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
347
- }
348
- const prefixSlash = prefix ? `${prefix}/` : void 0;
349
- const prefixQuery = prefix ? `${prefix}?` : void 0;
368
+ const prefixes = normalizePrefixes(options.prefix);
350
369
  let moostMiddleware = null;
351
370
  let localFetchTeardown = null;
352
371
  /** In middleware mode: maps req → next() for the onNoMatch callback */ const pendingNextMap = /* @__PURE__ */ new WeakMap();
@@ -415,7 +434,7 @@ function moostVite(options) {
415
434
  const serverDefines = {
416
435
  "process.env.MOOST_DEFERRED_ENV": "\"production\"",
417
436
  __MOOST_ENTRY__: JSON.stringify(`./${entryKey}.js`),
418
- __MOOST_PREFIX__: JSON.stringify(prefix || "/api")
437
+ __MOOST_PREFIX__: JSON.stringify(prefixes ?? null)
419
438
  };
420
439
  const ssrInput = {
421
440
  server: options.serverEntry || generatedServerEntry(cfg.root),
@@ -512,7 +531,7 @@ function moostVite(options) {
512
531
  entry: options.entry,
513
532
  ssrEntry: options.ssrEntry,
514
533
  serverEntry: options.serverEntry,
515
- prefix,
534
+ prefix: prefixes,
516
535
  port: options.port,
517
536
  ssrOutlet: options.ssrOutlet,
518
537
  ssrState: options.ssrState
@@ -585,10 +604,7 @@ function moostVite(options) {
585
604
  await ssrImport(options.entry);
586
605
  server.middlewares.use(async (req, res, next) => {
587
606
  await drainReload();
588
- if (options.middleware && prefix) {
589
- const url = req.url || "";
590
- if (url !== prefix && !url.startsWith(prefixSlash) && !url.startsWith(prefixQuery)) return next();
591
- }
607
+ if (options.middleware && prefixes && !matchesPrefix(req.url || "", prefixes)) return next();
592
608
  if (moostMiddleware) {
593
609
  if (options.middleware) {
594
610
  pendingNextMap.set(req, next);
package/dist/index.d.ts CHANGED
@@ -78,9 +78,10 @@ interface TMoostViteDevOptions {
78
78
  ssrFetch?: boolean;
79
79
  /**
80
80
  * Run Moost as Connect middleware instead of taking over the server.
81
- * When enabled, Moost handles only matching routes (guarded by `prefix`),
82
- * and unmatched requests fall through to Vite's default handler
83
- * (static assets, Vue/React pages, HMR client).
81
+ * When enabled, Moost runs first and unmatched requests fall through to
82
+ * Vite's default handler (static assets, Vue/React pages, HMR client).
83
+ * With `prefix` configured, requests outside the mount(s) skip the Moost
84
+ * router entirely (fast path).
84
85
  *
85
86
  * Use this for fullstack apps where Vite serves the frontend (Vue, React, etc.)
86
87
  * and Moost handles API routes.
@@ -89,13 +90,16 @@ interface TMoostViteDevOptions {
89
90
  */
90
91
  middleware?: boolean;
91
92
  /**
92
- * URL prefix for Moost API routes in middleware mode.
93
- * Only requests starting with this prefix are dispatched to Moost.
93
+ * URL mount(s) for Moost routes in middleware mode — a single prefix or a
94
+ * list of prefixes. Requests outside every mount skip the Moost router and
95
+ * go straight to the frontend handler (an optimization/guard, in both dev
96
+ * and the generated prod server). When omitted, every request enters Moost
97
+ * first and unmatched routes fall through to the frontend.
94
98
  * Ignored when `middleware` is `false`.
95
99
  *
96
- * Example: `'/api'`
100
+ * Example: `'/api'` or `['/api', '/.well-known']`
97
101
  */
98
- prefix?: string;
102
+ prefix?: string | string[];
99
103
  /**
100
104
  * Vue/React SSR entry module path. Used by the SSR server helper
101
105
  * (`@moostjs/vite/server`) for server-side rendering.
package/dist/index.mjs CHANGED
@@ -13,6 +13,31 @@ function entryBasename(entry) {
13
13
  return entry.split("/").pop().replace(/\.ts$/, ".js");
14
14
  }
15
15
  /**
16
+ * Normalizes the `prefix` option (single mount or list of mounts) into a list of
17
+ * normalized entries: leading slash ensured, trailing slash stripped.
18
+ * Returns `undefined` when no prefix is configured (empty input included) —
19
+ * the "route everything through Moost first" mode.
20
+ */ function normalizePrefixes(prefix) {
21
+ if (!prefix) return;
22
+ const normalized = (Array.isArray(prefix) ? prefix : [prefix]).filter(Boolean).map((entry) => {
23
+ let value = entry;
24
+ if (!value.startsWith("/")) value = `/${value}`;
25
+ if (value.endsWith("/")) value = value.slice(0, -1);
26
+ return value;
27
+ });
28
+ return normalized.length > 0 ? normalized : void 0;
29
+ }
30
+ /**
31
+ * Checks whether a request url falls under any of the normalized prefix mounts:
32
+ * exact match, a path segment below it, or the mount itself with a query string.
33
+ */ function matchesPrefix(url, prefixes) {
34
+ return prefixes.some((prefix) => {
35
+ if (!url.startsWith(prefix)) return false;
36
+ const next = url[prefix.length];
37
+ return next === void 0 || next === "/" || next === "?";
38
+ });
39
+ }
40
+ /**
16
41
  * Recursively gathers all importer modules upstream from a given module.
17
42
  *
18
43
  * @param {EnvironmentModuleNode | undefined} moduleNode - The starting module node.
@@ -309,13 +334,7 @@ function generatedServerEntry(root) {
309
334
  }
310
335
  function moostVite(options) {
311
336
  const externals = options.externals ?? true;
312
- let prefix = options.prefix;
313
- if (prefix) {
314
- if (!prefix.startsWith("/")) prefix = `/${prefix}`;
315
- if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
316
- }
317
- const prefixSlash = prefix ? `${prefix}/` : void 0;
318
- const prefixQuery = prefix ? `${prefix}?` : void 0;
337
+ const prefixes = normalizePrefixes(options.prefix);
319
338
  let moostMiddleware = null;
320
339
  let localFetchTeardown = null;
321
340
  /** In middleware mode: maps req → next() for the onNoMatch callback */ const pendingNextMap = /* @__PURE__ */ new WeakMap();
@@ -384,7 +403,7 @@ function moostVite(options) {
384
403
  const serverDefines = {
385
404
  "process.env.MOOST_DEFERRED_ENV": "\"production\"",
386
405
  __MOOST_ENTRY__: JSON.stringify(`./${entryKey}.js`),
387
- __MOOST_PREFIX__: JSON.stringify(prefix || "/api")
406
+ __MOOST_PREFIX__: JSON.stringify(prefixes ?? null)
388
407
  };
389
408
  const ssrInput = {
390
409
  server: options.serverEntry || generatedServerEntry(cfg.root),
@@ -481,7 +500,7 @@ function moostVite(options) {
481
500
  entry: options.entry,
482
501
  ssrEntry: options.ssrEntry,
483
502
  serverEntry: options.serverEntry,
484
- prefix,
503
+ prefix: prefixes,
485
504
  port: options.port,
486
505
  ssrOutlet: options.ssrOutlet,
487
506
  ssrState: options.ssrState
@@ -554,10 +573,7 @@ function moostVite(options) {
554
573
  await ssrImport(options.entry);
555
574
  server.middlewares.use(async (req, res, next) => {
556
575
  await drainReload();
557
- if (options.middleware && prefix) {
558
- const url = req.url || "";
559
- if (url !== prefix && !url.startsWith(prefixSlash) && !url.startsWith(prefixQuery)) return next();
560
- }
576
+ if (options.middleware && prefixes && !matchesPrefix(req.url || "", prefixes)) return next();
561
577
  if (moostMiddleware) {
562
578
  if (options.middleware) {
563
579
  pendingNextMap.set(req, next);
@@ -5,6 +5,31 @@ let moost = require("moost");
5
5
  const PLUGIN_NAME = "moost-vite";
6
6
  const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
7
7
  const DEFAULT_SSR_STATE = "<!--ssr-state-->";
8
+ /**
9
+ * Normalizes the `prefix` option (single mount or list of mounts) into a list of
10
+ * normalized entries: leading slash ensured, trailing slash stripped.
11
+ * Returns `undefined` when no prefix is configured (empty input included) —
12
+ * the "route everything through Moost first" mode.
13
+ */ function normalizePrefixes(prefix) {
14
+ if (!prefix) return;
15
+ const normalized = (Array.isArray(prefix) ? prefix : [prefix]).filter(Boolean).map((entry) => {
16
+ let value = entry;
17
+ if (!value.startsWith("/")) value = `/${value}`;
18
+ if (value.endsWith("/")) value = value.slice(0, -1);
19
+ return value;
20
+ });
21
+ return normalized.length > 0 ? normalized : void 0;
22
+ }
23
+ /**
24
+ * Checks whether a request url falls under any of the normalized prefix mounts:
25
+ * exact match, a path segment below it, or the mount itself with a query string.
26
+ */ function matchesPrefix(url, prefixes) {
27
+ return prefixes.some((prefix) => {
28
+ if (!url.startsWith(prefix)) return false;
29
+ const next = url[prefix.length];
30
+ return next === void 0 || next === "/" || next === "?";
31
+ });
32
+ }
8
33
  const logger = (0, moost.createLogger)({ level: 99 }).createTopic("\x1B[2m\x1B[36m" + PLUGIN_NAME);
9
34
 
10
35
  //#endregion
@@ -66,10 +91,7 @@ async function createSSRServer(options) {
66
91
  const clientDir = opts.clientDir || path.resolve(__dirname, "../client");
67
92
  const ssrOutlet = opts.ssrOutlet || (__MOOST_SSR_OUTLET__ !== void 0 ? __MOOST_SSR_OUTLET__ : "<!--ssr-outlet-->");
68
93
  const ssrState = opts.ssrState || (__MOOST_SSR_STATE__ !== void 0 ? __MOOST_SSR_STATE__ : "<!--ssr-state-->");
69
- let prefix = opts.prefix || __MOOST_PREFIX__;
70
- if (!prefix.startsWith("/")) prefix = `/${prefix}`;
71
- if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
72
- const prefixSlash = `${prefix}/`;
94
+ const prefixes = normalizePrefixes(opts.prefix ?? __MOOST_PREFIX__);
73
95
  const defaultPort = opts.port || Number(process.env.PORT) || 3e3;
74
96
  const template = await fs.readFile(path.resolve(clientDir, "index.html"), "utf8");
75
97
  let render = null;
@@ -83,7 +105,7 @@ async function createSSRServer(options) {
83
105
  let captured = false;
84
106
  const origListen = MoostHttp.prototype.listen;
85
107
  MoostHttp.prototype.listen = function(...args) {
86
- moostHandler = this.getServerCb();
108
+ moostHandler = this.getServerCb((req, res) => serveStatic(req, res));
87
109
  moostHttpRef.instance = this;
88
110
  captured = true;
89
111
  setTimeout(() => args.filter((a) => typeof a === "function").forEach((a) => a()), 1);
@@ -108,6 +130,32 @@ async function createSSRServer(options) {
108
130
  if (enableLocalFetch && moostHttpRef.instance) enableLocalFetch(moostHttpRef.instance);
109
131
  const sirv = (await import("sirv")).default;
110
132
  const serve = sirv(path.resolve(clientDir), { extensions: [] });
133
+ const serveStatic = (req, res) => {
134
+ const url = req.url || "/";
135
+ serve(req, res, async () => {
136
+ if (req.method !== "GET") {
137
+ res.statusCode = 404;
138
+ res.end();
139
+ return;
140
+ }
141
+ try {
142
+ if (render) {
143
+ const { html: appHtml, state } = await render(url);
144
+ res.statusCode = 200;
145
+ res.setHeader("Content-Type", "text/html");
146
+ res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}<\/script>` : ""));
147
+ } else {
148
+ res.statusCode = 200;
149
+ res.setHeader("Content-Type", "text/html");
150
+ res.end(template);
151
+ }
152
+ } catch (error) {
153
+ console.error(error);
154
+ res.statusCode = 500;
155
+ res.end(error.message);
156
+ }
157
+ });
158
+ };
111
159
  return {
112
160
  use(mw) {
113
161
  userMiddlewares.push(mw);
@@ -122,33 +170,11 @@ async function createSSRServer(options) {
122
170
  userMiddlewares[i++](req, res, runNext);
123
171
  return;
124
172
  }
125
- if (moostHandler && (url.startsWith(prefixSlash) || url === prefix || url.startsWith(`${prefix}?`))) {
173
+ if (moostHandler && (!prefixes || matchesPrefix(url, prefixes))) {
126
174
  moostHandler(req, res);
127
175
  return;
128
176
  }
129
- serve(req, res, async () => {
130
- if (req.method !== "GET") {
131
- res.statusCode = 404;
132
- res.end();
133
- return;
134
- }
135
- try {
136
- if (render) {
137
- const { html: appHtml, state } = await render(url);
138
- res.statusCode = 200;
139
- res.setHeader("Content-Type", "text/html");
140
- res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}<\/script>` : ""));
141
- } else {
142
- res.statusCode = 200;
143
- res.setHeader("Content-Type", "text/html");
144
- res.end(template);
145
- }
146
- } catch (error) {
147
- console.error(error);
148
- res.statusCode = 500;
149
- res.end(error.message);
150
- }
151
- });
177
+ serveStatic(req, res);
152
178
  };
153
179
  runNext();
154
180
  }).listen(p, () => {
@@ -6,8 +6,13 @@ interface TSSRServerOptions {
6
6
  entry?: (() => Promise<unknown>) | string;
7
7
  /** Override: SSR entry module path (e.g., '/src/entry-server.ts') */
8
8
  ssrEntry?: string;
9
- /** Override: URL prefix for API routes (e.g., '/api') */
10
- prefix?: string;
9
+ /**
10
+ * Override: URL mount(s) for Moost routes (e.g., '/api' or ['/api', '/.well-known']).
11
+ * Requests outside every mount skip the Moost router (optimization/guard).
12
+ * When omitted (and not baked by the plugin), every request enters Moost first
13
+ * and unmatched routes fall through to static/SSR — same as dev.
14
+ */
15
+ prefix?: string | string[];
11
16
  /** Override: port number (default: process.env.PORT || 3000) */
12
17
  port?: number;
13
18
  /** Override: client build directory (default: 'dist/client') */
@@ -6,6 +6,31 @@ import { createLogger } from "moost";
6
6
  const PLUGIN_NAME = "moost-vite";
7
7
  const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
8
8
  const DEFAULT_SSR_STATE = "<!--ssr-state-->";
9
+ /**
10
+ * Normalizes the `prefix` option (single mount or list of mounts) into a list of
11
+ * normalized entries: leading slash ensured, trailing slash stripped.
12
+ * Returns `undefined` when no prefix is configured (empty input included) —
13
+ * the "route everything through Moost first" mode.
14
+ */ function normalizePrefixes(prefix) {
15
+ if (!prefix) return;
16
+ const normalized = (Array.isArray(prefix) ? prefix : [prefix]).filter(Boolean).map((entry) => {
17
+ let value = entry;
18
+ if (!value.startsWith("/")) value = `/${value}`;
19
+ if (value.endsWith("/")) value = value.slice(0, -1);
20
+ return value;
21
+ });
22
+ return normalized.length > 0 ? normalized : void 0;
23
+ }
24
+ /**
25
+ * Checks whether a request url falls under any of the normalized prefix mounts:
26
+ * exact match, a path segment below it, or the mount itself with a query string.
27
+ */ function matchesPrefix(url, prefixes) {
28
+ return prefixes.some((prefix) => {
29
+ if (!url.startsWith(prefix)) return false;
30
+ const next = url[prefix.length];
31
+ return next === void 0 || next === "/" || next === "?";
32
+ });
33
+ }
9
34
  const logger = createLogger({ level: 99 }).createTopic("\x1B[2m\x1B[36m" + PLUGIN_NAME);
10
35
 
11
36
  //#endregion
@@ -67,10 +92,7 @@ async function createSSRServer(options) {
67
92
  const clientDir = opts.clientDir || path.resolve(import.meta.dirname, "../client");
68
93
  const ssrOutlet = opts.ssrOutlet || (__MOOST_SSR_OUTLET__ !== void 0 ? __MOOST_SSR_OUTLET__ : "<!--ssr-outlet-->");
69
94
  const ssrState = opts.ssrState || (__MOOST_SSR_STATE__ !== void 0 ? __MOOST_SSR_STATE__ : "<!--ssr-state-->");
70
- let prefix = opts.prefix || __MOOST_PREFIX__;
71
- if (!prefix.startsWith("/")) prefix = `/${prefix}`;
72
- if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
73
- const prefixSlash = `${prefix}/`;
95
+ const prefixes = normalizePrefixes(opts.prefix ?? __MOOST_PREFIX__);
74
96
  const defaultPort = opts.port || Number(process.env.PORT) || 3e3;
75
97
  const template = await fs.readFile(path.resolve(clientDir, "index.html"), "utf8");
76
98
  let render = null;
@@ -84,7 +106,7 @@ async function createSSRServer(options) {
84
106
  let captured = false;
85
107
  const origListen = MoostHttp.prototype.listen;
86
108
  MoostHttp.prototype.listen = function(...args) {
87
- moostHandler = this.getServerCb();
109
+ moostHandler = this.getServerCb((req, res) => serveStatic(req, res));
88
110
  moostHttpRef.instance = this;
89
111
  captured = true;
90
112
  setTimeout(() => args.filter((a) => typeof a === "function").forEach((a) => a()), 1);
@@ -109,6 +131,32 @@ async function createSSRServer(options) {
109
131
  if (enableLocalFetch && moostHttpRef.instance) enableLocalFetch(moostHttpRef.instance);
110
132
  const sirv = (await import("sirv")).default;
111
133
  const serve = sirv(path.resolve(clientDir), { extensions: [] });
134
+ const serveStatic = (req, res) => {
135
+ const url = req.url || "/";
136
+ serve(req, res, async () => {
137
+ if (req.method !== "GET") {
138
+ res.statusCode = 404;
139
+ res.end();
140
+ return;
141
+ }
142
+ try {
143
+ if (render) {
144
+ const { html: appHtml, state } = await render(url);
145
+ res.statusCode = 200;
146
+ res.setHeader("Content-Type", "text/html");
147
+ res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}<\/script>` : ""));
148
+ } else {
149
+ res.statusCode = 200;
150
+ res.setHeader("Content-Type", "text/html");
151
+ res.end(template);
152
+ }
153
+ } catch (error) {
154
+ console.error(error);
155
+ res.statusCode = 500;
156
+ res.end(error.message);
157
+ }
158
+ });
159
+ };
112
160
  return {
113
161
  use(mw) {
114
162
  userMiddlewares.push(mw);
@@ -123,33 +171,11 @@ async function createSSRServer(options) {
123
171
  userMiddlewares[i++](req, res, runNext);
124
172
  return;
125
173
  }
126
- if (moostHandler && (url.startsWith(prefixSlash) || url === prefix || url.startsWith(`${prefix}?`))) {
174
+ if (moostHandler && (!prefixes || matchesPrefix(url, prefixes))) {
127
175
  moostHandler(req, res);
128
176
  return;
129
177
  }
130
- serve(req, res, async () => {
131
- if (req.method !== "GET") {
132
- res.statusCode = 404;
133
- res.end();
134
- return;
135
- }
136
- try {
137
- if (render) {
138
- const { html: appHtml, state } = await render(url);
139
- res.statusCode = 200;
140
- res.setHeader("Content-Type", "text/html");
141
- res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}<\/script>` : ""));
142
- } else {
143
- res.statusCode = 200;
144
- res.setHeader("Content-Type", "text/html");
145
- res.end(template);
146
- }
147
- } catch (error) {
148
- console.error(error);
149
- res.statusCode = 500;
150
- res.end(error.message);
151
- }
152
- });
178
+ serveStatic(req, res);
153
179
  };
154
180
  runNext();
155
181
  }).listen(p, () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moostjs/vite",
3
- "version": "0.6.26",
3
+ "version": "0.6.27",
4
4
  "description": "Vite Dev plugin for moostjs",
5
5
  "keywords": [
6
6
  "composables",
@@ -58,8 +58,8 @@
58
58
  "peerDependencies": {
59
59
  "sirv": "^3.0.0",
60
60
  "vite": "^8.0.0",
61
- "@moostjs/event-http": "^0.6.26",
62
- "moost": "^0.6.26"
61
+ "@moostjs/event-http": "^0.6.27",
62
+ "moost": "^0.6.27"
63
63
  },
64
64
  "peerDependenciesMeta": {
65
65
  "sirv": {