@moostjs/vite 0.6.25 → 0.6.26

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 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 literally (appending only `/^@moostjs\/vite($|\/)/` so its own `define:` substitutions still land):
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 (backend mode only) |
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` | — | URL prefix filter for middleware mode (e.g. `'/api'`) |
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
@@ -379,6 +379,31 @@ function moostVite(options) {
379
379
  * Drains any pending Moost app reload before a request is served. Assigned once
380
380
  * the SSR module runner exists (see configureServer); a no-op until then.
381
381
  */ let drainReload = () => Promise.resolve();
382
+ /**
383
+ * Last Moost entry import failure. While set, the dev middleware answers matching
384
+ * requests with 502 instead of letting them fall through to the SPA/SSR fallback
385
+ * (which would serve index.html for API routes). Cleared on a successful reload;
386
+ * the next hot update schedules the retry.
387
+ */ let bootError = null;
388
+ /**
389
+ * Looks up the Moost entry node with the verbatim `options.entry` string — the
390
+ * module graph keys (and memoizes) unresolved urls as-is, so the same spelling
391
+ * that was passed to `runner.import()` is guaranteed to return the very node
392
+ * that import created.
393
+ */ const getEntryModule = (moduleGraph) => moduleGraph.getModuleByUrl(options.entry);
394
+ /**
395
+ * Drops the captured Moost app so the next request triggers a full reload:
396
+ * releases the middleware + local fetch and ejects DI instances affected by
397
+ * the changed modules.
398
+ */ const ejectApp = (cleanupInstances) => {
399
+ moostMiddleware = null;
400
+ if (localFetchTeardown) {
401
+ localFetchTeardown();
402
+ localFetchTeardown = null;
403
+ }
404
+ moostRestartCleanup(adapters, options.onEject, cleanupInstances);
405
+ reloadRequired = true;
406
+ };
382
407
  patchMoostHandlerLogging();
383
408
  const pluginConfig = {
384
409
  name: PLUGIN_NAME,
@@ -523,16 +548,27 @@ function moostVite(options) {
523
548
  };
524
549
  },
525
550
  async configureServer(server) {
526
- const runner = (0, vite.createServerModuleRunner)(server.environments.ssr);
551
+ const runner = (0, vite.createServerModuleRunner)(server.environments.ssr, { hmr: false });
527
552
  const ssrImport = (id) => runner.import(id);
553
+ bootError = null;
554
+ reloadRequired = false;
555
+ reloadPromise = null;
528
556
  const runReload = () => {
529
557
  reloadRequired = false;
530
558
  console.log();
531
559
  logger.debug("🚀 Reloading Moost App...");
532
560
  console.log();
533
561
  return (async () => {
534
- for (const adapter of adapters) if (adapter.detected) await adapter.init();
535
- await ssrImport(options.entry);
562
+ try {
563
+ for (const adapter of adapters) if (adapter.detected) await adapter.init();
564
+ const entryModule = await getEntryModule(server.environments.ssr.moduleGraph);
565
+ if (entryModule?.transformResult) server.environments.ssr.moduleGraph.invalidateModule(entryModule);
566
+ await ssrImport(options.entry);
567
+ bootError = null;
568
+ } catch (error) {
569
+ bootError = error;
570
+ logger.error(`✖️ Failed to reload Moost App: ${error.message}`);
571
+ }
536
572
  await new Promise((resolve) => setTimeout(resolve, 1));
537
573
  })().finally(() => {
538
574
  reloadPromise = null;
@@ -561,30 +597,29 @@ function moostVite(options) {
561
597
  }
562
598
  return moostMiddleware(req, res);
563
599
  }
600
+ if (bootError) {
601
+ res.statusCode = 502;
602
+ res.setHeader("Content-Type", "text/plain");
603
+ res.end(`Moost app failed to load: ${bootError.message}`);
604
+ return;
605
+ }
564
606
  next();
565
607
  });
566
608
  },
567
- hotUpdate({ file }) {
568
- if (file.endsWith(".ts")) {
569
- const modules = this.environment.moduleGraph.getModulesByFile(file);
570
- if (modules) {
571
- logger.debug(`🔃 Hot update: ${file}`);
572
- const cleanupInstances = /* @__PURE__ */ new Set();
573
- for (const mod of modules) {
574
- const allImporters = gatherAllImporters(mod);
575
- for (const impModule of allImporters) if (impModule.id) cleanupInstances.add(impModule.id);
576
- this.environment.moduleGraph.invalidateModule(mod);
577
- }
578
- moostMiddleware = null;
579
- if (localFetchTeardown) {
580
- localFetchTeardown();
581
- localFetchTeardown = null;
582
- }
583
- moostRestartCleanup(adapters, options.onEject, cleanupInstances);
584
- reloadRequired = true;
585
- }
586
- return [];
587
- }
609
+ async hotUpdate({ file, modules }) {
610
+ if (this.environment.name !== "ssr" || modules.length === 0) return;
611
+ const { moduleGraph } = this.environment;
612
+ const entryModule = await getEntryModule(moduleGraph);
613
+ const importerSets = modules.map((mod) => gatherAllImporters(mod));
614
+ if (entryModule && !importerSets.some((importers) => importers.has(entryModule))) return;
615
+ logger.debug(`🔃 Hot update: ${file}`);
616
+ const cleanupInstances = /* @__PURE__ */ new Set();
617
+ for (const importers of importerSets) for (const impModule of importers) if (impModule.id) cleanupInstances.add(impModule.id);
618
+ for (const mod of modules) moduleGraph.invalidateModule(mod);
619
+ if (entryModule) moduleGraph.invalidateModule(entryModule);
620
+ else moduleGraph.invalidateAll();
621
+ ejectApp(cleanupInstances);
622
+ return [];
588
623
  }
589
624
  };
590
625
  if (isProd || isTest) {
@@ -606,7 +641,7 @@ function moostVite(options) {
606
641
  const url = req.originalUrl || req.url || "/";
607
642
  try {
608
643
  await drainReload();
609
- let template = await fs.readFile((0, node_path.resolve)(process.cwd(), "index.html"), "utf8");
644
+ let template = await fs.readFile((0, node_path.resolve)(server.config.root, "index.html"), "utf8");
610
645
  template = await server.transformIndexHtml(url, template);
611
646
  const { render } = await server.ssrLoadModule(options.ssrEntry);
612
647
  const { html: appHtml, state } = await render(url);
package/dist/index.mjs CHANGED
@@ -348,6 +348,31 @@ function moostVite(options) {
348
348
  * Drains any pending Moost app reload before a request is served. Assigned once
349
349
  * the SSR module runner exists (see configureServer); a no-op until then.
350
350
  */ let drainReload = () => Promise.resolve();
351
+ /**
352
+ * Last Moost entry import failure. While set, the dev middleware answers matching
353
+ * requests with 502 instead of letting them fall through to the SPA/SSR fallback
354
+ * (which would serve index.html for API routes). Cleared on a successful reload;
355
+ * the next hot update schedules the retry.
356
+ */ let bootError = null;
357
+ /**
358
+ * Looks up the Moost entry node with the verbatim `options.entry` string — the
359
+ * module graph keys (and memoizes) unresolved urls as-is, so the same spelling
360
+ * that was passed to `runner.import()` is guaranteed to return the very node
361
+ * that import created.
362
+ */ const getEntryModule = (moduleGraph) => moduleGraph.getModuleByUrl(options.entry);
363
+ /**
364
+ * Drops the captured Moost app so the next request triggers a full reload:
365
+ * releases the middleware + local fetch and ejects DI instances affected by
366
+ * the changed modules.
367
+ */ const ejectApp = (cleanupInstances) => {
368
+ moostMiddleware = null;
369
+ if (localFetchTeardown) {
370
+ localFetchTeardown();
371
+ localFetchTeardown = null;
372
+ }
373
+ moostRestartCleanup(adapters, options.onEject, cleanupInstances);
374
+ reloadRequired = true;
375
+ };
351
376
  patchMoostHandlerLogging();
352
377
  return [{
353
378
  name: PLUGIN_NAME,
@@ -492,16 +517,27 @@ function moostVite(options) {
492
517
  };
493
518
  },
494
519
  async configureServer(server) {
495
- const runner = createServerModuleRunner(server.environments.ssr);
520
+ const runner = createServerModuleRunner(server.environments.ssr, { hmr: false });
496
521
  const ssrImport = (id) => runner.import(id);
522
+ bootError = null;
523
+ reloadRequired = false;
524
+ reloadPromise = null;
497
525
  const runReload = () => {
498
526
  reloadRequired = false;
499
527
  console.log();
500
528
  logger.debug("🚀 Reloading Moost App...");
501
529
  console.log();
502
530
  return (async () => {
503
- for (const adapter of adapters) if (adapter.detected) await adapter.init();
504
- await ssrImport(options.entry);
531
+ try {
532
+ for (const adapter of adapters) if (adapter.detected) await adapter.init();
533
+ const entryModule = await getEntryModule(server.environments.ssr.moduleGraph);
534
+ if (entryModule?.transformResult) server.environments.ssr.moduleGraph.invalidateModule(entryModule);
535
+ await ssrImport(options.entry);
536
+ bootError = null;
537
+ } catch (error) {
538
+ bootError = error;
539
+ logger.error(`✖️ Failed to reload Moost App: ${error.message}`);
540
+ }
505
541
  await new Promise((resolve) => setTimeout(resolve, 1));
506
542
  })().finally(() => {
507
543
  reloadPromise = null;
@@ -530,30 +566,29 @@ function moostVite(options) {
530
566
  }
531
567
  return moostMiddleware(req, res);
532
568
  }
569
+ if (bootError) {
570
+ res.statusCode = 502;
571
+ res.setHeader("Content-Type", "text/plain");
572
+ res.end(`Moost app failed to load: ${bootError.message}`);
573
+ return;
574
+ }
533
575
  next();
534
576
  });
535
577
  },
536
- hotUpdate({ file }) {
537
- if (file.endsWith(".ts")) {
538
- const modules = this.environment.moduleGraph.getModulesByFile(file);
539
- if (modules) {
540
- logger.debug(`🔃 Hot update: ${file}`);
541
- const cleanupInstances = /* @__PURE__ */ new Set();
542
- for (const mod of modules) {
543
- const allImporters = gatherAllImporters(mod);
544
- for (const impModule of allImporters) if (impModule.id) cleanupInstances.add(impModule.id);
545
- this.environment.moduleGraph.invalidateModule(mod);
546
- }
547
- moostMiddleware = null;
548
- if (localFetchTeardown) {
549
- localFetchTeardown();
550
- localFetchTeardown = null;
551
- }
552
- moostRestartCleanup(adapters, options.onEject, cleanupInstances);
553
- reloadRequired = true;
554
- }
555
- return [];
556
- }
578
+ async hotUpdate({ file, modules }) {
579
+ if (this.environment.name !== "ssr" || modules.length === 0) return;
580
+ const { moduleGraph } = this.environment;
581
+ const entryModule = await getEntryModule(moduleGraph);
582
+ const importerSets = modules.map((mod) => gatherAllImporters(mod));
583
+ if (entryModule && !importerSets.some((importers) => importers.has(entryModule))) return;
584
+ logger.debug(`🔃 Hot update: ${file}`);
585
+ const cleanupInstances = /* @__PURE__ */ new Set();
586
+ for (const importers of importerSets) for (const impModule of importers) if (impModule.id) cleanupInstances.add(impModule.id);
587
+ for (const mod of modules) moduleGraph.invalidateModule(mod);
588
+ if (entryModule) moduleGraph.invalidateModule(entryModule);
589
+ else moduleGraph.invalidateAll();
590
+ ejectApp(cleanupInstances);
591
+ return [];
557
592
  }
558
593
  }, options.middleware && options.ssrEntry ? {
559
594
  name: `${PLUGIN_NAME}:ssr-fallback`,
@@ -567,7 +602,7 @@ function moostVite(options) {
567
602
  const url = req.originalUrl || req.url || "/";
568
603
  try {
569
604
  await drainReload();
570
- let template = await fs.readFile(resolve(process.cwd(), "index.html"), "utf8");
605
+ let template = await fs.readFile(resolve(server.config.root, "index.html"), "utf8");
571
606
  template = await server.transformIndexHtml(url, template);
572
607
  const { render } = await server.ssrLoadModule(options.ssrEntry);
573
608
  const { html: appHtml, state } = await render(url);
@@ -33,7 +33,7 @@ async function createSSRServer(options) {
33
33
  if (req.method !== "GET") return next();
34
34
  const url = req.originalUrl || req.url || "/";
35
35
  try {
36
- let template = await fs.readFile(path.resolve(process.cwd(), "index.html"), "utf8");
36
+ let template = await fs.readFile(path.resolve(vite.config.root, "index.html"), "utf8");
37
37
  template = await vite.transformIndexHtml(url, template);
38
38
  const { render } = await vite.ssrLoadModule(ssrEntry);
39
39
  const { html: appHtml, state } = await render(url);
@@ -34,7 +34,7 @@ async function createSSRServer(options) {
34
34
  if (req.method !== "GET") return next();
35
35
  const url = req.originalUrl || req.url || "/";
36
36
  try {
37
- let template = await fs.readFile(path.resolve(process.cwd(), "index.html"), "utf8");
37
+ let template = await fs.readFile(path.resolve(vite.config.root, "index.html"), "utf8");
38
38
  template = await vite.transformIndexHtml(url, template);
39
39
  const { render } = await vite.ssrLoadModule(ssrEntry);
40
40
  const { html: appHtml, state } = await render(url);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moostjs/vite",
3
- "version": "0.6.25",
3
+ "version": "0.6.26",
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.25",
62
- "moost": "^0.6.25"
61
+ "@moostjs/event-http": "^0.6.26",
62
+ "moost": "^0.6.26"
63
63
  },
64
64
  "peerDependenciesMeta": {
65
65
  "sirv": {