@moostjs/vite 0.6.25 → 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/README.md +5 -3
- package/dist/index.cjs +89 -38
- package/dist/index.d.ts +11 -7
- package/dist/index.mjs +89 -38
- package/dist/prod-server.cjs +56 -30
- package/dist/prod-server.d.ts +7 -2
- package/dist/prod-server.mjs +56 -30
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -101,6 +101,8 @@ moostVite({
|
|
|
101
101
|
})
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
Note that the production server generated by `vite build` defaults the prefix to `'/api'` when the option is omitted — set `prefix` explicitly so dev (where all requests pass through Moost without it) and prod route the same way.
|
|
105
|
+
|
|
104
106
|
## SSR Mode
|
|
105
107
|
|
|
106
108
|
Add `ssrEntry` to enable server-side rendering. The plugin handles everything automatically — SSR rendering in dev, multi-environment builds for production:
|
|
@@ -180,7 +182,7 @@ export default defineConfig({
|
|
|
180
182
|
|
|
181
183
|
**Don't externalize:** workspace packages (`workspace:*`), anything that uses `Symbol()` as a public slot key (Moost, wooks, atscript), anything not reliably hoisted by pnpm.
|
|
182
184
|
|
|
183
|
-
You can also opt out of bundle-everything by setting `ssr.noExternal` to an explicit list — the plugin honors it
|
|
185
|
+
You can also opt out of bundle-everything by setting `ssr.noExternal` to an explicit list — the plugin honors it, appending `/^@moostjs\/vite($|\/)/` (so its own `define:` substitutions still land) plus, unless you externalize the runtime yourself, the moost/wooks runtime patterns described below:
|
|
184
186
|
|
|
185
187
|
```ts
|
|
186
188
|
ssr: {
|
|
@@ -225,12 +227,12 @@ The plugin injects a `__VITE_ID` decorator on `@Injectable` and `@Controller` cl
|
|
|
225
227
|
| `host` | `string` | `'localhost'` | Dev server host (backend mode only) |
|
|
226
228
|
| `outDir` | `string` | `'dist'` | Build output directory (backend mode only) |
|
|
227
229
|
| `format` | `'cjs' \| 'esm'` | `'esm'` | Output module format (backend mode only) |
|
|
228
|
-
| `sourcemap` | `boolean` | `true` | Generate source maps (
|
|
230
|
+
| `sourcemap` | `boolean` | `true` | Generate source maps (both modes; in middleware mode applies to the `dist/server` SSR build) |
|
|
229
231
|
| `externals` | `boolean \| object` | `true` | Configure external dependencies (backend mode only) |
|
|
230
232
|
| `onEject` | `function` | — | Hook to control DI instance ejection during HMR |
|
|
231
233
|
| `ssrFetch` | `boolean` | `true` | Enable local fetch interception for SSR |
|
|
232
234
|
| `middleware` | `boolean` | `false` | Run Moost as Connect middleware (Vite serves frontend) |
|
|
233
|
-
| `prefix` | `string` |
|
|
235
|
+
| `prefix` | `string` | `'/api'` (prod server) | URL prefix filter for middleware mode (e.g. `'/api'`); the production server defaults to `'/api'` when omitted |
|
|
234
236
|
| `ssrEntry` | `string` | — | Vue/React SSR entry module (e.g. `'/src/entry-server.ts'`) |
|
|
235
237
|
| `ssrOutlet` | `string` | `'<!--ssr-outlet-->'` | HTML placeholder for SSR-rendered content |
|
|
236
238
|
| `ssrState` | `string` | `'<!--ssr-state-->'` | HTML placeholder for SSR state transfer script |
|
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
|
-
|
|
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();
|
|
@@ -379,6 +398,31 @@ function moostVite(options) {
|
|
|
379
398
|
* Drains any pending Moost app reload before a request is served. Assigned once
|
|
380
399
|
* the SSR module runner exists (see configureServer); a no-op until then.
|
|
381
400
|
*/ let drainReload = () => Promise.resolve();
|
|
401
|
+
/**
|
|
402
|
+
* Last Moost entry import failure. While set, the dev middleware answers matching
|
|
403
|
+
* requests with 502 instead of letting them fall through to the SPA/SSR fallback
|
|
404
|
+
* (which would serve index.html for API routes). Cleared on a successful reload;
|
|
405
|
+
* the next hot update schedules the retry.
|
|
406
|
+
*/ let bootError = null;
|
|
407
|
+
/**
|
|
408
|
+
* Looks up the Moost entry node with the verbatim `options.entry` string — the
|
|
409
|
+
* module graph keys (and memoizes) unresolved urls as-is, so the same spelling
|
|
410
|
+
* that was passed to `runner.import()` is guaranteed to return the very node
|
|
411
|
+
* that import created.
|
|
412
|
+
*/ const getEntryModule = (moduleGraph) => moduleGraph.getModuleByUrl(options.entry);
|
|
413
|
+
/**
|
|
414
|
+
* Drops the captured Moost app so the next request triggers a full reload:
|
|
415
|
+
* releases the middleware + local fetch and ejects DI instances affected by
|
|
416
|
+
* the changed modules.
|
|
417
|
+
*/ const ejectApp = (cleanupInstances) => {
|
|
418
|
+
moostMiddleware = null;
|
|
419
|
+
if (localFetchTeardown) {
|
|
420
|
+
localFetchTeardown();
|
|
421
|
+
localFetchTeardown = null;
|
|
422
|
+
}
|
|
423
|
+
moostRestartCleanup(adapters, options.onEject, cleanupInstances);
|
|
424
|
+
reloadRequired = true;
|
|
425
|
+
};
|
|
382
426
|
patchMoostHandlerLogging();
|
|
383
427
|
const pluginConfig = {
|
|
384
428
|
name: PLUGIN_NAME,
|
|
@@ -390,7 +434,7 @@ function moostVite(options) {
|
|
|
390
434
|
const serverDefines = {
|
|
391
435
|
"process.env.MOOST_DEFERRED_ENV": "\"production\"",
|
|
392
436
|
__MOOST_ENTRY__: JSON.stringify(`./${entryKey}.js`),
|
|
393
|
-
__MOOST_PREFIX__: JSON.stringify(
|
|
437
|
+
__MOOST_PREFIX__: JSON.stringify(prefixes ?? null)
|
|
394
438
|
};
|
|
395
439
|
const ssrInput = {
|
|
396
440
|
server: options.serverEntry || generatedServerEntry(cfg.root),
|
|
@@ -487,7 +531,7 @@ function moostVite(options) {
|
|
|
487
531
|
entry: options.entry,
|
|
488
532
|
ssrEntry: options.ssrEntry,
|
|
489
533
|
serverEntry: options.serverEntry,
|
|
490
|
-
prefix,
|
|
534
|
+
prefix: prefixes,
|
|
491
535
|
port: options.port,
|
|
492
536
|
ssrOutlet: options.ssrOutlet,
|
|
493
537
|
ssrState: options.ssrState
|
|
@@ -523,16 +567,27 @@ function moostVite(options) {
|
|
|
523
567
|
};
|
|
524
568
|
},
|
|
525
569
|
async configureServer(server) {
|
|
526
|
-
const runner = (0, vite.createServerModuleRunner)(server.environments.ssr);
|
|
570
|
+
const runner = (0, vite.createServerModuleRunner)(server.environments.ssr, { hmr: false });
|
|
527
571
|
const ssrImport = (id) => runner.import(id);
|
|
572
|
+
bootError = null;
|
|
573
|
+
reloadRequired = false;
|
|
574
|
+
reloadPromise = null;
|
|
528
575
|
const runReload = () => {
|
|
529
576
|
reloadRequired = false;
|
|
530
577
|
console.log();
|
|
531
578
|
logger.debug("🚀 Reloading Moost App...");
|
|
532
579
|
console.log();
|
|
533
580
|
return (async () => {
|
|
534
|
-
|
|
535
|
-
|
|
581
|
+
try {
|
|
582
|
+
for (const adapter of adapters) if (adapter.detected) await adapter.init();
|
|
583
|
+
const entryModule = await getEntryModule(server.environments.ssr.moduleGraph);
|
|
584
|
+
if (entryModule?.transformResult) server.environments.ssr.moduleGraph.invalidateModule(entryModule);
|
|
585
|
+
await ssrImport(options.entry);
|
|
586
|
+
bootError = null;
|
|
587
|
+
} catch (error) {
|
|
588
|
+
bootError = error;
|
|
589
|
+
logger.error(`✖️ Failed to reload Moost App: ${error.message}`);
|
|
590
|
+
}
|
|
536
591
|
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
537
592
|
})().finally(() => {
|
|
538
593
|
reloadPromise = null;
|
|
@@ -549,10 +604,7 @@ function moostVite(options) {
|
|
|
549
604
|
await ssrImport(options.entry);
|
|
550
605
|
server.middlewares.use(async (req, res, next) => {
|
|
551
606
|
await drainReload();
|
|
552
|
-
if (options.middleware &&
|
|
553
|
-
const url = req.url || "";
|
|
554
|
-
if (url !== prefix && !url.startsWith(prefixSlash) && !url.startsWith(prefixQuery)) return next();
|
|
555
|
-
}
|
|
607
|
+
if (options.middleware && prefixes && !matchesPrefix(req.url || "", prefixes)) return next();
|
|
556
608
|
if (moostMiddleware) {
|
|
557
609
|
if (options.middleware) {
|
|
558
610
|
pendingNextMap.set(req, next);
|
|
@@ -561,30 +613,29 @@ function moostVite(options) {
|
|
|
561
613
|
}
|
|
562
614
|
return moostMiddleware(req, res);
|
|
563
615
|
}
|
|
616
|
+
if (bootError) {
|
|
617
|
+
res.statusCode = 502;
|
|
618
|
+
res.setHeader("Content-Type", "text/plain");
|
|
619
|
+
res.end(`Moost app failed to load: ${bootError.message}`);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
564
622
|
next();
|
|
565
623
|
});
|
|
566
624
|
},
|
|
567
|
-
hotUpdate({ file }) {
|
|
568
|
-
if (
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
localFetchTeardown = null;
|
|
582
|
-
}
|
|
583
|
-
moostRestartCleanup(adapters, options.onEject, cleanupInstances);
|
|
584
|
-
reloadRequired = true;
|
|
585
|
-
}
|
|
586
|
-
return [];
|
|
587
|
-
}
|
|
625
|
+
async hotUpdate({ file, modules }) {
|
|
626
|
+
if (this.environment.name !== "ssr" || modules.length === 0) return;
|
|
627
|
+
const { moduleGraph } = this.environment;
|
|
628
|
+
const entryModule = await getEntryModule(moduleGraph);
|
|
629
|
+
const importerSets = modules.map((mod) => gatherAllImporters(mod));
|
|
630
|
+
if (entryModule && !importerSets.some((importers) => importers.has(entryModule))) return;
|
|
631
|
+
logger.debug(`🔃 Hot update: ${file}`);
|
|
632
|
+
const cleanupInstances = /* @__PURE__ */ new Set();
|
|
633
|
+
for (const importers of importerSets) for (const impModule of importers) if (impModule.id) cleanupInstances.add(impModule.id);
|
|
634
|
+
for (const mod of modules) moduleGraph.invalidateModule(mod);
|
|
635
|
+
if (entryModule) moduleGraph.invalidateModule(entryModule);
|
|
636
|
+
else moduleGraph.invalidateAll();
|
|
637
|
+
ejectApp(cleanupInstances);
|
|
638
|
+
return [];
|
|
588
639
|
}
|
|
589
640
|
};
|
|
590
641
|
if (isProd || isTest) {
|
|
@@ -606,7 +657,7 @@ function moostVite(options) {
|
|
|
606
657
|
const url = req.originalUrl || req.url || "/";
|
|
607
658
|
try {
|
|
608
659
|
await drainReload();
|
|
609
|
-
let template = await fs.readFile((0, node_path.resolve)(
|
|
660
|
+
let template = await fs.readFile((0, node_path.resolve)(server.config.root, "index.html"), "utf8");
|
|
610
661
|
template = await server.transformIndexHtml(url, template);
|
|
611
662
|
const { render } = await server.ssrLoadModule(options.ssrEntry);
|
|
612
663
|
const { html: appHtml, state } = await render(url);
|
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
|
|
82
|
-
*
|
|
83
|
-
*
|
|
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
|
|
93
|
-
*
|
|
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
|
-
|
|
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();
|
|
@@ -348,6 +367,31 @@ function moostVite(options) {
|
|
|
348
367
|
* Drains any pending Moost app reload before a request is served. Assigned once
|
|
349
368
|
* the SSR module runner exists (see configureServer); a no-op until then.
|
|
350
369
|
*/ let drainReload = () => Promise.resolve();
|
|
370
|
+
/**
|
|
371
|
+
* Last Moost entry import failure. While set, the dev middleware answers matching
|
|
372
|
+
* requests with 502 instead of letting them fall through to the SPA/SSR fallback
|
|
373
|
+
* (which would serve index.html for API routes). Cleared on a successful reload;
|
|
374
|
+
* the next hot update schedules the retry.
|
|
375
|
+
*/ let bootError = null;
|
|
376
|
+
/**
|
|
377
|
+
* Looks up the Moost entry node with the verbatim `options.entry` string — the
|
|
378
|
+
* module graph keys (and memoizes) unresolved urls as-is, so the same spelling
|
|
379
|
+
* that was passed to `runner.import()` is guaranteed to return the very node
|
|
380
|
+
* that import created.
|
|
381
|
+
*/ const getEntryModule = (moduleGraph) => moduleGraph.getModuleByUrl(options.entry);
|
|
382
|
+
/**
|
|
383
|
+
* Drops the captured Moost app so the next request triggers a full reload:
|
|
384
|
+
* releases the middleware + local fetch and ejects DI instances affected by
|
|
385
|
+
* the changed modules.
|
|
386
|
+
*/ const ejectApp = (cleanupInstances) => {
|
|
387
|
+
moostMiddleware = null;
|
|
388
|
+
if (localFetchTeardown) {
|
|
389
|
+
localFetchTeardown();
|
|
390
|
+
localFetchTeardown = null;
|
|
391
|
+
}
|
|
392
|
+
moostRestartCleanup(adapters, options.onEject, cleanupInstances);
|
|
393
|
+
reloadRequired = true;
|
|
394
|
+
};
|
|
351
395
|
patchMoostHandlerLogging();
|
|
352
396
|
return [{
|
|
353
397
|
name: PLUGIN_NAME,
|
|
@@ -359,7 +403,7 @@ function moostVite(options) {
|
|
|
359
403
|
const serverDefines = {
|
|
360
404
|
"process.env.MOOST_DEFERRED_ENV": "\"production\"",
|
|
361
405
|
__MOOST_ENTRY__: JSON.stringify(`./${entryKey}.js`),
|
|
362
|
-
__MOOST_PREFIX__: JSON.stringify(
|
|
406
|
+
__MOOST_PREFIX__: JSON.stringify(prefixes ?? null)
|
|
363
407
|
};
|
|
364
408
|
const ssrInput = {
|
|
365
409
|
server: options.serverEntry || generatedServerEntry(cfg.root),
|
|
@@ -456,7 +500,7 @@ function moostVite(options) {
|
|
|
456
500
|
entry: options.entry,
|
|
457
501
|
ssrEntry: options.ssrEntry,
|
|
458
502
|
serverEntry: options.serverEntry,
|
|
459
|
-
prefix,
|
|
503
|
+
prefix: prefixes,
|
|
460
504
|
port: options.port,
|
|
461
505
|
ssrOutlet: options.ssrOutlet,
|
|
462
506
|
ssrState: options.ssrState
|
|
@@ -492,16 +536,27 @@ function moostVite(options) {
|
|
|
492
536
|
};
|
|
493
537
|
},
|
|
494
538
|
async configureServer(server) {
|
|
495
|
-
const runner = createServerModuleRunner(server.environments.ssr);
|
|
539
|
+
const runner = createServerModuleRunner(server.environments.ssr, { hmr: false });
|
|
496
540
|
const ssrImport = (id) => runner.import(id);
|
|
541
|
+
bootError = null;
|
|
542
|
+
reloadRequired = false;
|
|
543
|
+
reloadPromise = null;
|
|
497
544
|
const runReload = () => {
|
|
498
545
|
reloadRequired = false;
|
|
499
546
|
console.log();
|
|
500
547
|
logger.debug("🚀 Reloading Moost App...");
|
|
501
548
|
console.log();
|
|
502
549
|
return (async () => {
|
|
503
|
-
|
|
504
|
-
|
|
550
|
+
try {
|
|
551
|
+
for (const adapter of adapters) if (adapter.detected) await adapter.init();
|
|
552
|
+
const entryModule = await getEntryModule(server.environments.ssr.moduleGraph);
|
|
553
|
+
if (entryModule?.transformResult) server.environments.ssr.moduleGraph.invalidateModule(entryModule);
|
|
554
|
+
await ssrImport(options.entry);
|
|
555
|
+
bootError = null;
|
|
556
|
+
} catch (error) {
|
|
557
|
+
bootError = error;
|
|
558
|
+
logger.error(`✖️ Failed to reload Moost App: ${error.message}`);
|
|
559
|
+
}
|
|
505
560
|
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
506
561
|
})().finally(() => {
|
|
507
562
|
reloadPromise = null;
|
|
@@ -518,10 +573,7 @@ function moostVite(options) {
|
|
|
518
573
|
await ssrImport(options.entry);
|
|
519
574
|
server.middlewares.use(async (req, res, next) => {
|
|
520
575
|
await drainReload();
|
|
521
|
-
if (options.middleware &&
|
|
522
|
-
const url = req.url || "";
|
|
523
|
-
if (url !== prefix && !url.startsWith(prefixSlash) && !url.startsWith(prefixQuery)) return next();
|
|
524
|
-
}
|
|
576
|
+
if (options.middleware && prefixes && !matchesPrefix(req.url || "", prefixes)) return next();
|
|
525
577
|
if (moostMiddleware) {
|
|
526
578
|
if (options.middleware) {
|
|
527
579
|
pendingNextMap.set(req, next);
|
|
@@ -530,30 +582,29 @@ function moostVite(options) {
|
|
|
530
582
|
}
|
|
531
583
|
return moostMiddleware(req, res);
|
|
532
584
|
}
|
|
585
|
+
if (bootError) {
|
|
586
|
+
res.statusCode = 502;
|
|
587
|
+
res.setHeader("Content-Type", "text/plain");
|
|
588
|
+
res.end(`Moost app failed to load: ${bootError.message}`);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
533
591
|
next();
|
|
534
592
|
});
|
|
535
593
|
},
|
|
536
|
-
hotUpdate({ file }) {
|
|
537
|
-
if (
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
localFetchTeardown = null;
|
|
551
|
-
}
|
|
552
|
-
moostRestartCleanup(adapters, options.onEject, cleanupInstances);
|
|
553
|
-
reloadRequired = true;
|
|
554
|
-
}
|
|
555
|
-
return [];
|
|
556
|
-
}
|
|
594
|
+
async hotUpdate({ file, modules }) {
|
|
595
|
+
if (this.environment.name !== "ssr" || modules.length === 0) return;
|
|
596
|
+
const { moduleGraph } = this.environment;
|
|
597
|
+
const entryModule = await getEntryModule(moduleGraph);
|
|
598
|
+
const importerSets = modules.map((mod) => gatherAllImporters(mod));
|
|
599
|
+
if (entryModule && !importerSets.some((importers) => importers.has(entryModule))) return;
|
|
600
|
+
logger.debug(`🔃 Hot update: ${file}`);
|
|
601
|
+
const cleanupInstances = /* @__PURE__ */ new Set();
|
|
602
|
+
for (const importers of importerSets) for (const impModule of importers) if (impModule.id) cleanupInstances.add(impModule.id);
|
|
603
|
+
for (const mod of modules) moduleGraph.invalidateModule(mod);
|
|
604
|
+
if (entryModule) moduleGraph.invalidateModule(entryModule);
|
|
605
|
+
else moduleGraph.invalidateAll();
|
|
606
|
+
ejectApp(cleanupInstances);
|
|
607
|
+
return [];
|
|
557
608
|
}
|
|
558
609
|
}, options.middleware && options.ssrEntry ? {
|
|
559
610
|
name: `${PLUGIN_NAME}:ssr-fallback`,
|
|
@@ -567,7 +618,7 @@ function moostVite(options) {
|
|
|
567
618
|
const url = req.originalUrl || req.url || "/";
|
|
568
619
|
try {
|
|
569
620
|
await drainReload();
|
|
570
|
-
let template = await fs.readFile(resolve(
|
|
621
|
+
let template = await fs.readFile(resolve(server.config.root, "index.html"), "utf8");
|
|
571
622
|
template = await server.transformIndexHtml(url, template);
|
|
572
623
|
const { render } = await server.ssrLoadModule(options.ssrEntry);
|
|
573
624
|
const { html: appHtml, state } = await render(url);
|
package/dist/prod-server.cjs
CHANGED
|
@@ -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
|
|
@@ -33,7 +58,7 @@ async function createSSRServer(options) {
|
|
|
33
58
|
if (req.method !== "GET") return next();
|
|
34
59
|
const url = req.originalUrl || req.url || "/";
|
|
35
60
|
try {
|
|
36
|
-
let template = await fs.readFile(path.resolve(
|
|
61
|
+
let template = await fs.readFile(path.resolve(vite.config.root, "index.html"), "utf8");
|
|
37
62
|
template = await vite.transformIndexHtml(url, template);
|
|
38
63
|
const { render } = await vite.ssrLoadModule(ssrEntry);
|
|
39
64
|
const { html: appHtml, state } = await render(url);
|
|
@@ -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
|
-
|
|
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 && (
|
|
173
|
+
if (moostHandler && (!prefixes || matchesPrefix(url, prefixes))) {
|
|
126
174
|
moostHandler(req, res);
|
|
127
175
|
return;
|
|
128
176
|
}
|
|
129
|
-
|
|
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, () => {
|
package/dist/prod-server.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
10
|
-
|
|
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') */
|
package/dist/prod-server.mjs
CHANGED
|
@@ -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
|
|
@@ -34,7 +59,7 @@ async function createSSRServer(options) {
|
|
|
34
59
|
if (req.method !== "GET") return next();
|
|
35
60
|
const url = req.originalUrl || req.url || "/";
|
|
36
61
|
try {
|
|
37
|
-
let template = await fs.readFile(path.resolve(
|
|
62
|
+
let template = await fs.readFile(path.resolve(vite.config.root, "index.html"), "utf8");
|
|
38
63
|
template = await vite.transformIndexHtml(url, template);
|
|
39
64
|
const { render } = await vite.ssrLoadModule(ssrEntry);
|
|
40
65
|
const { html: appHtml, state } = await render(url);
|
|
@@ -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
|
-
|
|
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 && (
|
|
174
|
+
if (moostHandler && (!prefixes || matchesPrefix(url, prefixes))) {
|
|
127
175
|
moostHandler(req, res);
|
|
128
176
|
return;
|
|
129
177
|
}
|
|
130
|
-
|
|
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.
|
|
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.
|
|
62
|
-
"moost": "^0.6.
|
|
61
|
+
"@moostjs/event-http": "^0.6.27",
|
|
62
|
+
"moost": "^0.6.27"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|
|
65
65
|
"sirv": {
|