@moostjs/vite 0.6.53 → 0.7.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/README.md +4 -3
- package/dist/index.cjs +45 -4
- package/dist/index.d.ts +14 -2
- package/dist/index.mjs +46 -5
- package/dist/prod-server.cjs +16 -5
- package/dist/prod-server.d.ts +8 -0
- package/dist/prod-server.mjs +17 -6
- package/package.json +5 -10
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ app.init()
|
|
|
92
92
|
|
|
93
93
|
Requests matching Moost routes (e.g. `/api/hello/world`) are handled by Moost with the full pipeline (interceptors, DI, validation). Everything else (`/`, `/about`, static assets) falls through to Vite.
|
|
94
94
|
|
|
95
|
-
The optional `prefix` option
|
|
95
|
+
The optional `prefix` option declares the API mount(s) (a string or a list) — requests outside every mount skip Moost entirely, and an unmatched route under a mount gets Moost's `404` JSON error instead of the frontend's `index.html` — a static file under the mount (e.g. `/.well-known/security.txt`) is still served for `GET`/`HEAD` — and `prefixFallthrough: true` lets unmatched routes fall through instead:
|
|
96
96
|
|
|
97
97
|
```ts
|
|
98
98
|
moostVite({
|
|
@@ -102,7 +102,7 @@ moostVite({
|
|
|
102
102
|
})
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
Without `prefix`, every request enters Moost first and unmatched routes fall through to the frontend — in dev and in the generated production server alike.
|
|
106
106
|
|
|
107
107
|
## SSR Mode
|
|
108
108
|
|
|
@@ -272,7 +272,8 @@ The plugin injects a `__VITE_ID` decorator on `@Injectable` and `@Controller` cl
|
|
|
272
272
|
| `onEject` | `function` | — | Hook to control DI instance ejection during HMR |
|
|
273
273
|
| `ssrFetch` | `boolean` | `true` | Enable local fetch interception for SSR |
|
|
274
274
|
| `middleware` | `boolean` | `false` | Run Moost as Connect middleware (Vite serves frontend) |
|
|
275
|
-
| `prefix` | `string`
|
|
275
|
+
| `prefix` | `string \| string[]` | — | API mount(s) for middleware mode: requests outside skip Moost, unmatched routes inside get Moost's `404` |
|
|
276
|
+
| `prefixFallthrough` | `boolean` | `false` | Let unmatched routes under `prefix` fall through to the frontend instead of Moost's `404` |
|
|
276
277
|
| `ssrEntry` | `string` | — | Vue/React SSR entry module (e.g. `'/src/entry-server.ts'`) |
|
|
277
278
|
| `ssrOutlet` | `string` | `'<!--ssr-outlet-->'` | HTML placeholder for SSR-rendered content |
|
|
278
279
|
| `ssrState` | `string` | `'<!--ssr-state-->'` | HTML placeholder for SSR state transfer script |
|
package/dist/index.cjs
CHANGED
|
@@ -816,6 +816,45 @@ const DEFAULT_PRECOMPRESS_INCLUDE = /\.(?:m?js|cjs|css|html?|svg|json|webmanifes
|
|
|
816
816
|
gzip: opts.gzip !== false
|
|
817
817
|
};
|
|
818
818
|
}
|
|
819
|
+
/** The decoded pathname of a request url, the way sirv looks files up. */ function requestPathname(url) {
|
|
820
|
+
const end = url.search(/[?#]/);
|
|
821
|
+
const pathname = end === -1 ? url : url.slice(0, end);
|
|
822
|
+
if (!pathname.includes("%")) return pathname;
|
|
823
|
+
try {
|
|
824
|
+
return decodeURI(pathname);
|
|
825
|
+
} catch {
|
|
826
|
+
return pathname;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* The no-match handler of an API mount (a `prefix` without `prefixFallthrough`): a GET/HEAD
|
|
831
|
+
* that names a static file is served (`tryStatic`, with `notFound` for a miss); anything else
|
|
832
|
+
* gets Moost's own 404 — never the SPA shell or an SSR page.
|
|
833
|
+
*/ function apiMountNoMatch(tryStatic, notFound) {
|
|
834
|
+
return (req, res) => {
|
|
835
|
+
if (req.method === "GET" || req.method === "HEAD") tryStatic(req, res, notFound);
|
|
836
|
+
else notFound(req, res);
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
/** Resolved public-dir roots (`publicDir` is fixed per dev server). */ const publicRoots = /* @__PURE__ */ new Map();
|
|
840
|
+
/**
|
|
841
|
+
* Whether `url` names a file inside `publicDir` (Vite's `public/`), for the dev server: a GET
|
|
842
|
+
* under an API mount that no route matched is served from there before Moost's 404.
|
|
843
|
+
*/ function isPublicFile(publicDir, url) {
|
|
844
|
+
if (!publicDir) return false;
|
|
845
|
+
let root = publicRoots.get(publicDir);
|
|
846
|
+
if (!root) {
|
|
847
|
+
root = (0, node_path.resolve)(publicDir);
|
|
848
|
+
publicRoots.set(publicDir, root);
|
|
849
|
+
}
|
|
850
|
+
const file = (0, node_path.resolve)(root, `.${requestPathname(url)}`);
|
|
851
|
+
if (file !== root && !file.startsWith(root + node_path.sep)) return false;
|
|
852
|
+
try {
|
|
853
|
+
return (0, node_fs.statSync)(file, { throwIfNoEntry: false })?.isFile() ?? false;
|
|
854
|
+
} catch {
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
819
858
|
|
|
820
859
|
//#endregion
|
|
821
860
|
//#region packages/vite/src/moost-vite.ts
|
|
@@ -923,10 +962,10 @@ function moostVite(options) {
|
|
|
923
962
|
if (bootingGeneration !== bootGeneration) logger.error(`⚠️ A stale Moost boot captured the HTTP middleware (boot generation ${bootingGeneration}, latest ${bootGeneration}) — an HMR reload race; a follow-up reload will replace it.`);
|
|
924
963
|
httpCaptured = true;
|
|
925
964
|
moostHttpRef.current = this;
|
|
926
|
-
if (options.middleware)
|
|
927
|
-
pendingNextMap.get(req)?.();
|
|
928
|
-
|
|
929
|
-
else moostMiddleware = this.getServerCb();
|
|
965
|
+
if (options.middleware) {
|
|
966
|
+
const toVite = (req) => pendingNextMap.get(req)?.();
|
|
967
|
+
moostMiddleware = this.getServerCb(prefixes && !options.prefixFallthrough ? apiMountNoMatch((req, res, miss) => isPublicFile(resolvedConfig?.publicDir, req.url || "/") ? toVite(req) : miss(req, res), this.getServerCb()) : toVite);
|
|
968
|
+
} else moostMiddleware = this.getServerCb();
|
|
930
969
|
if (options.ssrFetch !== false && moduleExports?.enableLocalFetch) {
|
|
931
970
|
if (localFetchTeardown) localFetchTeardown();
|
|
932
971
|
localFetchTeardown = moduleExports.enableLocalFetch(this);
|
|
@@ -1046,6 +1085,7 @@ function moostVite(options) {
|
|
|
1046
1085
|
"process.env.MOOST_DEFERRED_ENV": "\"production\"",
|
|
1047
1086
|
__MOOST_ENTRY__: JSON.stringify(`./${entryKey}.js`),
|
|
1048
1087
|
__MOOST_PREFIX__: JSON.stringify(prefixes ?? null),
|
|
1088
|
+
__MOOST_PREFIX_FALLTHROUGH__: JSON.stringify(options.prefixFallthrough === true),
|
|
1049
1089
|
__MOOST_SSR_FORWARDING__: JSON.stringify(ssrForwarding),
|
|
1050
1090
|
__MOOST_SSR_ENTRY__: "undefined",
|
|
1051
1091
|
__MOOST_SSR_OUTLET__: JSON.stringify(ssrOutlet),
|
|
@@ -1155,6 +1195,7 @@ function moostVite(options) {
|
|
|
1155
1195
|
ssrEntry: options.ssrEntry,
|
|
1156
1196
|
serverEntry: options.serverEntry,
|
|
1157
1197
|
prefix: prefixes,
|
|
1198
|
+
prefixFallthrough: options.prefixFallthrough,
|
|
1158
1199
|
ssrOutlet: options.ssrOutlet,
|
|
1159
1200
|
ssrState: options.ssrState,
|
|
1160
1201
|
ssrHead: options.ssrHead,
|
package/dist/index.d.ts
CHANGED
|
@@ -146,7 +146,8 @@ interface TMoostViteDevOptions {
|
|
|
146
146
|
* When enabled, Moost runs first and unmatched requests fall through to
|
|
147
147
|
* Vite's default handler (static assets, Vue/React pages, HMR client).
|
|
148
148
|
* With `prefix` configured, requests outside the mount(s) skip the Moost
|
|
149
|
-
* router entirely (fast path)
|
|
149
|
+
* router entirely (fast path), and unmatched routes inside a mount get
|
|
150
|
+
* Moost's 404 (see `prefix`).
|
|
150
151
|
*
|
|
151
152
|
* Use this for fullstack apps where Vite serves the frontend (Vue, React, etc.)
|
|
152
153
|
* and Moost handles API routes.
|
|
@@ -158,13 +159,24 @@ interface TMoostViteDevOptions {
|
|
|
158
159
|
* URL mount(s) for Moost routes in middleware mode — a single prefix or a
|
|
159
160
|
* list of prefixes. Requests outside every mount skip the Moost router and
|
|
160
161
|
* go straight to the frontend handler (an optimization/guard, in both dev
|
|
161
|
-
* and the generated prod server).
|
|
162
|
+
* and the generated prod server). A prefix is an API mount: an unmatched
|
|
163
|
+
* route under it gets Moost's own 404 (JSON) in dev and production instead
|
|
164
|
+
* of the SPA shell or an SSR page — set `prefixFallthrough: true` to let it
|
|
165
|
+
* fall through to the frontend. When omitted, every request enters Moost
|
|
162
166
|
* first and unmatched routes fall through to the frontend.
|
|
163
167
|
* Ignored when `middleware` is `false`.
|
|
164
168
|
*
|
|
165
169
|
* Example: `'/api'` or `['/api', '/.well-known']`
|
|
166
170
|
*/
|
|
167
171
|
prefix?: string | string[];
|
|
172
|
+
/**
|
|
173
|
+
* Let unmatched routes under `prefix` fall through to the frontend (static
|
|
174
|
+
* files, SPA shell, SSR page) instead of answering Moost's 404.
|
|
175
|
+
* Applies in dev and is baked into the generated production server.
|
|
176
|
+
*
|
|
177
|
+
* Default: `false`.
|
|
178
|
+
*/
|
|
179
|
+
prefixFallthrough?: boolean;
|
|
168
180
|
/**
|
|
169
181
|
* Vue/React SSR entry module path. Used by the SSR server helper
|
|
170
182
|
* (`@moostjs/vite/server`) for server-side rendering.
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { createServerModuleRunner } from "vite";
|
|
4
4
|
import MagicString from "magic-string";
|
|
@@ -787,6 +787,45 @@ const DEFAULT_PRECOMPRESS_INCLUDE = /\.(?:m?js|cjs|css|html?|svg|json|webmanifes
|
|
|
787
787
|
gzip: opts.gzip !== false
|
|
788
788
|
};
|
|
789
789
|
}
|
|
790
|
+
/** The decoded pathname of a request url, the way sirv looks files up. */ function requestPathname(url) {
|
|
791
|
+
const end = url.search(/[?#]/);
|
|
792
|
+
const pathname = end === -1 ? url : url.slice(0, end);
|
|
793
|
+
if (!pathname.includes("%")) return pathname;
|
|
794
|
+
try {
|
|
795
|
+
return decodeURI(pathname);
|
|
796
|
+
} catch {
|
|
797
|
+
return pathname;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* The no-match handler of an API mount (a `prefix` without `prefixFallthrough`): a GET/HEAD
|
|
802
|
+
* that names a static file is served (`tryStatic`, with `notFound` for a miss); anything else
|
|
803
|
+
* gets Moost's own 404 — never the SPA shell or an SSR page.
|
|
804
|
+
*/ function apiMountNoMatch(tryStatic, notFound) {
|
|
805
|
+
return (req, res) => {
|
|
806
|
+
if (req.method === "GET" || req.method === "HEAD") tryStatic(req, res, notFound);
|
|
807
|
+
else notFound(req, res);
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
/** Resolved public-dir roots (`publicDir` is fixed per dev server). */ const publicRoots = /* @__PURE__ */ new Map();
|
|
811
|
+
/**
|
|
812
|
+
* Whether `url` names a file inside `publicDir` (Vite's `public/`), for the dev server: a GET
|
|
813
|
+
* under an API mount that no route matched is served from there before Moost's 404.
|
|
814
|
+
*/ function isPublicFile(publicDir, url) {
|
|
815
|
+
if (!publicDir) return false;
|
|
816
|
+
let root = publicRoots.get(publicDir);
|
|
817
|
+
if (!root) {
|
|
818
|
+
root = resolve(publicDir);
|
|
819
|
+
publicRoots.set(publicDir, root);
|
|
820
|
+
}
|
|
821
|
+
const file = resolve(root, `.${requestPathname(url)}`);
|
|
822
|
+
if (file !== root && !file.startsWith(root + sep)) return false;
|
|
823
|
+
try {
|
|
824
|
+
return statSync(file, { throwIfNoEntry: false })?.isFile() ?? false;
|
|
825
|
+
} catch {
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
790
829
|
|
|
791
830
|
//#endregion
|
|
792
831
|
//#region packages/vite/src/moost-vite.ts
|
|
@@ -892,10 +931,10 @@ function moostVite(options) {
|
|
|
892
931
|
if (bootingGeneration !== bootGeneration) logger.error(`⚠️ A stale Moost boot captured the HTTP middleware (boot generation ${bootingGeneration}, latest ${bootGeneration}) — an HMR reload race; a follow-up reload will replace it.`);
|
|
893
932
|
httpCaptured = true;
|
|
894
933
|
moostHttpRef.current = this;
|
|
895
|
-
if (options.middleware)
|
|
896
|
-
pendingNextMap.get(req)?.();
|
|
897
|
-
|
|
898
|
-
else moostMiddleware = this.getServerCb();
|
|
934
|
+
if (options.middleware) {
|
|
935
|
+
const toVite = (req) => pendingNextMap.get(req)?.();
|
|
936
|
+
moostMiddleware = this.getServerCb(prefixes && !options.prefixFallthrough ? apiMountNoMatch((req, res, miss) => isPublicFile(resolvedConfig?.publicDir, req.url || "/") ? toVite(req) : miss(req, res), this.getServerCb()) : toVite);
|
|
937
|
+
} else moostMiddleware = this.getServerCb();
|
|
899
938
|
if (options.ssrFetch !== false && moduleExports?.enableLocalFetch) {
|
|
900
939
|
if (localFetchTeardown) localFetchTeardown();
|
|
901
940
|
localFetchTeardown = moduleExports.enableLocalFetch(this);
|
|
@@ -1015,6 +1054,7 @@ function moostVite(options) {
|
|
|
1015
1054
|
"process.env.MOOST_DEFERRED_ENV": "\"production\"",
|
|
1016
1055
|
__MOOST_ENTRY__: JSON.stringify(`./${entryKey}.js`),
|
|
1017
1056
|
__MOOST_PREFIX__: JSON.stringify(prefixes ?? null),
|
|
1057
|
+
__MOOST_PREFIX_FALLTHROUGH__: JSON.stringify(options.prefixFallthrough === true),
|
|
1018
1058
|
__MOOST_SSR_FORWARDING__: JSON.stringify(ssrForwarding),
|
|
1019
1059
|
__MOOST_SSR_ENTRY__: "undefined",
|
|
1020
1060
|
__MOOST_SSR_OUTLET__: JSON.stringify(ssrOutlet),
|
|
@@ -1124,6 +1164,7 @@ function moostVite(options) {
|
|
|
1124
1164
|
ssrEntry: options.ssrEntry,
|
|
1125
1165
|
serverEntry: options.serverEntry,
|
|
1126
1166
|
prefix: prefixes,
|
|
1167
|
+
prefixFallthrough: options.prefixFallthrough,
|
|
1127
1168
|
ssrOutlet: options.ssrOutlet,
|
|
1128
1169
|
ssrState: options.ssrState,
|
|
1129
1170
|
ssrHead: options.ssrHead,
|
package/dist/prod-server.cjs
CHANGED
|
@@ -47,6 +47,16 @@ const DEFAULT_REVALIDATE_CACHE_CONTROL = "no-cache";
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
+
* The no-match handler of an API mount (a `prefix` without `prefixFallthrough`): a GET/HEAD
|
|
51
|
+
* that names a static file is served (`tryStatic`, with `notFound` for a miss); anything else
|
|
52
|
+
* gets Moost's own 404 — never the SPA shell or an SSR page.
|
|
53
|
+
*/ function apiMountNoMatch(tryStatic, notFound) {
|
|
54
|
+
return (req, res) => {
|
|
55
|
+
if (req.method === "GET" || req.method === "HEAD") tryStatic(req, res, notFound);
|
|
56
|
+
else notFound(req, res);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
50
60
|
* The root `index.html` (and precompressed siblings) is the SPA/SSR template:
|
|
51
61
|
* it is never served as a static file, but by the SPA/SSR fallback (in SSR mode
|
|
52
62
|
* a raw template would leak its unfilled `<!--ssr-outlet-->` placeholders).
|
|
@@ -111,7 +121,7 @@ const DEFAULT_REVALIDATE_CACHE_CONTROL = "no-cache";
|
|
|
111
121
|
/**
|
|
112
122
|
* Static file serving (sirv) with the cache policy and the precompressed
|
|
113
123
|
* variants; requests that match no file — and the root `index.html` template —
|
|
114
|
-
* go to `servePage` (SSR render or SPA shell).
|
|
124
|
+
* go to `fallback`, by default `servePage` (SSR render or SPA shell).
|
|
115
125
|
*/ async function createStaticHandler(clientDir, variants, cache, servePage) {
|
|
116
126
|
const { default: sirv } = await import("sirv");
|
|
117
127
|
const negotiate = variants.brotli || variants.gzip;
|
|
@@ -126,10 +136,10 @@ const DEFAULT_REVALIDATE_CACHE_CONTROL = "no-cache";
|
|
|
126
136
|
if (vary) res.setHeader("Vary", vary);
|
|
127
137
|
}
|
|
128
138
|
});
|
|
129
|
-
return (req, res) => {
|
|
139
|
+
return (req, res, fallback = servePage) => {
|
|
130
140
|
const pathname = requestPathname(req.url || "/");
|
|
131
141
|
if (isIndexTemplatePath(pathname)) {
|
|
132
|
-
|
|
142
|
+
fallback(req, res);
|
|
133
143
|
return;
|
|
134
144
|
}
|
|
135
145
|
const restore = [];
|
|
@@ -141,7 +151,7 @@ const DEFAULT_REVALIDATE_CACHE_CONTROL = "no-cache";
|
|
|
141
151
|
}
|
|
142
152
|
serve(req, res, () => {
|
|
143
153
|
for (const undo of restore) undo();
|
|
144
|
-
|
|
154
|
+
fallback(req, res);
|
|
145
155
|
});
|
|
146
156
|
};
|
|
147
157
|
}
|
|
@@ -304,6 +314,7 @@ async function createSSRServer(options) {
|
|
|
304
314
|
const ssrState = opts.ssrState || (__MOOST_SSR_STATE__ !== void 0 ? __MOOST_SSR_STATE__ : DEFAULT_SSR_STATE);
|
|
305
315
|
const ssrHead = opts.ssrHead || (__MOOST_SSR_HEAD__ !== void 0 ? __MOOST_SSR_HEAD__ : DEFAULT_SSR_HEAD);
|
|
306
316
|
const prefixes = normalizePrefixes(opts.prefix ?? __MOOST_PREFIX__);
|
|
317
|
+
const prefixFallthrough = opts.prefixFallthrough ?? __MOOST_PREFIX_FALLTHROUGH__ ?? false;
|
|
307
318
|
const ssrForwarding = opts.ssrFetchForwarding ?? __MOOST_SSR_FORWARDING__ ?? true;
|
|
308
319
|
const variants = resolvePrecompressed(__MOOST_PRECOMPRESS__, opts.precompressed);
|
|
309
320
|
const cache = resolveCacheControl(__MOOST_CACHE_CONTROL__, opts.cacheControl);
|
|
@@ -321,7 +332,7 @@ async function createSSRServer(options) {
|
|
|
321
332
|
let captured = false;
|
|
322
333
|
const origListen = MoostHttp.prototype.listen;
|
|
323
334
|
MoostHttp.prototype.listen = function(...args) {
|
|
324
|
-
moostHandler = this.getServerCb((req, res) => serveStatic(req, res));
|
|
335
|
+
moostHandler = this.getServerCb(prefixes && !prefixFallthrough ? apiMountNoMatch((req, res, miss) => serveStatic(req, res, miss), this.getServerCb()) : (req, res) => serveStatic(req, res));
|
|
325
336
|
moostHttpRef.instance = this;
|
|
326
337
|
captured = true;
|
|
327
338
|
setTimeout(() => args.filter((a) => typeof a === "function").forEach((a) => a()), 1);
|
package/dist/prod-server.d.ts
CHANGED
|
@@ -85,10 +85,18 @@ interface TSSRServerOptions {
|
|
|
85
85
|
/**
|
|
86
86
|
* Override: URL mount(s) for Moost routes (e.g., '/api' or ['/api', '/.well-known']).
|
|
87
87
|
* Requests outside every mount skip the Moost router (optimization/guard).
|
|
88
|
+
* A prefix is an API mount: an unmatched route under it gets Moost's own
|
|
89
|
+
* 404 (JSON), never the SPA shell or an SSR page (see `prefixFallthrough`).
|
|
88
90
|
* When omitted (and not baked by the plugin), every request enters Moost first
|
|
89
91
|
* and unmatched routes fall through to static/SSR — same as dev.
|
|
90
92
|
*/
|
|
91
93
|
prefix?: string | string[];
|
|
94
|
+
/**
|
|
95
|
+
* Override: let unmatched routes under `prefix` fall through to static files
|
|
96
|
+
* and the SPA/SSR page instead of answering Moost's 404.
|
|
97
|
+
* Default: baked from the plugin's `prefixFallthrough` option (`false`).
|
|
98
|
+
*/
|
|
99
|
+
prefixFallthrough?: boolean;
|
|
92
100
|
/** Override: port number (default: process.env.PORT || 3000) */
|
|
93
101
|
port?: number;
|
|
94
102
|
/** Override: bind address, e.g. '127.0.0.1' (default: process.env.HOST, else all interfaces) */
|
package/dist/prod-server.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { once } from "node:events";
|
|
2
|
+
import "node:fs";
|
|
2
3
|
import { readFile } from "node:fs/promises";
|
|
3
4
|
import { join } from "node:path";
|
|
4
|
-
import "node:fs";
|
|
5
5
|
import "node:module";
|
|
6
6
|
import { createLogger } from "moost";
|
|
7
7
|
|
|
@@ -48,6 +48,16 @@ const DEFAULT_REVALIDATE_CACHE_CONTROL = "no-cache";
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
+
* The no-match handler of an API mount (a `prefix` without `prefixFallthrough`): a GET/HEAD
|
|
52
|
+
* that names a static file is served (`tryStatic`, with `notFound` for a miss); anything else
|
|
53
|
+
* gets Moost's own 404 — never the SPA shell or an SSR page.
|
|
54
|
+
*/ function apiMountNoMatch(tryStatic, notFound) {
|
|
55
|
+
return (req, res) => {
|
|
56
|
+
if (req.method === "GET" || req.method === "HEAD") tryStatic(req, res, notFound);
|
|
57
|
+
else notFound(req, res);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
51
61
|
* The root `index.html` (and precompressed siblings) is the SPA/SSR template:
|
|
52
62
|
* it is never served as a static file, but by the SPA/SSR fallback (in SSR mode
|
|
53
63
|
* a raw template would leak its unfilled `<!--ssr-outlet-->` placeholders).
|
|
@@ -112,7 +122,7 @@ const DEFAULT_REVALIDATE_CACHE_CONTROL = "no-cache";
|
|
|
112
122
|
/**
|
|
113
123
|
* Static file serving (sirv) with the cache policy and the precompressed
|
|
114
124
|
* variants; requests that match no file — and the root `index.html` template —
|
|
115
|
-
* go to `servePage` (SSR render or SPA shell).
|
|
125
|
+
* go to `fallback`, by default `servePage` (SSR render or SPA shell).
|
|
116
126
|
*/ async function createStaticHandler(clientDir, variants, cache, servePage) {
|
|
117
127
|
const { default: sirv } = await import("sirv");
|
|
118
128
|
const negotiate = variants.brotli || variants.gzip;
|
|
@@ -127,10 +137,10 @@ const DEFAULT_REVALIDATE_CACHE_CONTROL = "no-cache";
|
|
|
127
137
|
if (vary) res.setHeader("Vary", vary);
|
|
128
138
|
}
|
|
129
139
|
});
|
|
130
|
-
return (req, res) => {
|
|
140
|
+
return (req, res, fallback = servePage) => {
|
|
131
141
|
const pathname = requestPathname(req.url || "/");
|
|
132
142
|
if (isIndexTemplatePath(pathname)) {
|
|
133
|
-
|
|
143
|
+
fallback(req, res);
|
|
134
144
|
return;
|
|
135
145
|
}
|
|
136
146
|
const restore = [];
|
|
@@ -142,7 +152,7 @@ const DEFAULT_REVALIDATE_CACHE_CONTROL = "no-cache";
|
|
|
142
152
|
}
|
|
143
153
|
serve(req, res, () => {
|
|
144
154
|
for (const undo of restore) undo();
|
|
145
|
-
|
|
155
|
+
fallback(req, res);
|
|
146
156
|
});
|
|
147
157
|
};
|
|
148
158
|
}
|
|
@@ -305,6 +315,7 @@ async function createSSRServer(options) {
|
|
|
305
315
|
const ssrState = opts.ssrState || (__MOOST_SSR_STATE__ !== void 0 ? __MOOST_SSR_STATE__ : DEFAULT_SSR_STATE);
|
|
306
316
|
const ssrHead = opts.ssrHead || (__MOOST_SSR_HEAD__ !== void 0 ? __MOOST_SSR_HEAD__ : DEFAULT_SSR_HEAD);
|
|
307
317
|
const prefixes = normalizePrefixes(opts.prefix ?? __MOOST_PREFIX__);
|
|
318
|
+
const prefixFallthrough = opts.prefixFallthrough ?? __MOOST_PREFIX_FALLTHROUGH__ ?? false;
|
|
308
319
|
const ssrForwarding = opts.ssrFetchForwarding ?? __MOOST_SSR_FORWARDING__ ?? true;
|
|
309
320
|
const variants = resolvePrecompressed(__MOOST_PRECOMPRESS__, opts.precompressed);
|
|
310
321
|
const cache = resolveCacheControl(__MOOST_CACHE_CONTROL__, opts.cacheControl);
|
|
@@ -322,7 +333,7 @@ async function createSSRServer(options) {
|
|
|
322
333
|
let captured = false;
|
|
323
334
|
const origListen = MoostHttp.prototype.listen;
|
|
324
335
|
MoostHttp.prototype.listen = function(...args) {
|
|
325
|
-
moostHandler = this.getServerCb((req, res) => serveStatic(req, res));
|
|
336
|
+
moostHandler = this.getServerCb(prefixes && !prefixFallthrough ? apiMountNoMatch((req, res, miss) => serveStatic(req, res, miss), this.getServerCb()) : (req, res) => serveStatic(req, res));
|
|
326
337
|
moostHttpRef.instance = this;
|
|
327
338
|
captured = true;
|
|
328
339
|
setTimeout(() => args.filter((a) => typeof a === "function").forEach((a) => a()), 1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moostjs/vite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Vite Dev plugin for moostjs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"composables",
|
|
@@ -50,21 +50,16 @@
|
|
|
50
50
|
]
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"magic-string": "^0.30.21"
|
|
53
|
+
"magic-string": "^0.30.21",
|
|
54
|
+
"sirv": "^3.0.2"
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"vitest": "3.2.7"
|
|
57
58
|
},
|
|
58
59
|
"peerDependencies": {
|
|
59
|
-
"sirv": "^3.0.0",
|
|
60
60
|
"vite": "^8.0.0",
|
|
61
|
-
"@moostjs/event-http": "^0.
|
|
62
|
-
"moost": "^0.
|
|
63
|
-
},
|
|
64
|
-
"peerDependenciesMeta": {
|
|
65
|
-
"sirv": {
|
|
66
|
-
"optional": true
|
|
67
|
-
}
|
|
61
|
+
"@moostjs/event-http": "^0.7.0",
|
|
62
|
+
"moost": "^0.7.0"
|
|
68
63
|
},
|
|
69
64
|
"scripts": {
|
|
70
65
|
"pub": "pnpm publish --access public",
|