@moostjs/vite 0.6.1 → 0.6.3

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
@@ -2,13 +2,19 @@
2
2
 
3
3
  Vite dev plugin for [Moost](https://moost.org). Enables hot module replacement for Moost HTTP applications during development, automatic adapter detection, and production build configuration.
4
4
 
5
+ Supports two modes:
6
+ - **Backend mode** (default) — Moost owns the server, Vite provides HMR and TypeScript transforms
7
+ - **Middleware mode** — Vite serves the frontend (Vue, React, etc.), Moost handles API routes as middleware
8
+
5
9
  ## Installation
6
10
 
7
11
  ```bash
8
12
  npm install @moostjs/vite --save-dev
9
13
  ```
10
14
 
11
- ## Quick Start
15
+ ## Backend Mode (default)
16
+
17
+ For pure API servers where Moost handles all HTTP requests. Vite is used as a dev tool for HMR and decorator transforms.
12
18
 
13
19
  ```ts
14
20
  // vite.config.ts
@@ -19,23 +25,124 @@ export default defineConfig({
19
25
  plugins: [
20
26
  moostVite({
21
27
  entry: './src/main.ts',
22
- port: 3000,
23
28
  }),
24
29
  ],
25
30
  })
26
31
  ```
27
32
 
33
+ ```ts
34
+ // src/main.ts
35
+ import { Moost, Param } from 'moost'
36
+ import { MoostHttp, Get } from '@moostjs/event-http'
37
+
38
+ class App extends Moost {
39
+ @Get('hello/:name')
40
+ hello(@Param('name') name: string) {
41
+ return { message: `Hello ${name}!` }
42
+ }
43
+ }
44
+
45
+ const app = new App()
46
+ const http = new MoostHttp()
47
+ app.adapter(http).listen(3000)
48
+ app.init()
49
+ ```
50
+
51
+ Run with `vite dev`. The plugin patches `MoostHttp.listen()` so Moost doesn't bind a port — Vite's dev server handles HTTP instead. Controller changes trigger HMR with automatic DI cleanup.
52
+
53
+ ## Middleware Mode
54
+
55
+ For fullstack apps where Vite serves the frontend and Moost handles API routes. Unmatched requests fall through to Vite's default handler (static assets, Vue/React pages, HMR client).
56
+
57
+ ```ts
58
+ // vite.config.ts
59
+ import { defineConfig } from 'vite'
60
+ import vue from '@vitejs/plugin-vue'
61
+ import { moostVite } from '@moostjs/vite'
62
+
63
+ export default defineConfig({
64
+ plugins: [
65
+ vue(),
66
+ moostVite({
67
+ entry: './src/api/main.ts',
68
+ middleware: true,
69
+ }),
70
+ ],
71
+ })
72
+ ```
73
+
74
+ ```ts
75
+ // src/api/main.ts
76
+ import { Moost, Param } from 'moost'
77
+ import { MoostHttp, Get } from '@moostjs/event-http'
78
+
79
+ class ApiController extends Moost {
80
+ @Get('api/hello/:name')
81
+ hello(@Param('name') name: string) {
82
+ return { message: `Hello ${name}!` }
83
+ }
84
+ }
85
+
86
+ const app = new ApiController()
87
+ const http = new MoostHttp()
88
+ app.adapter(http).listen(3000)
89
+ app.init()
90
+ ```
91
+
92
+ 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.
93
+
94
+ The optional `prefix` option adds a fast-path check — requests not matching the prefix skip Moost entirely:
95
+
96
+ ```ts
97
+ moostVite({
98
+ entry: './src/api/main.ts',
99
+ middleware: true,
100
+ prefix: '/api',
101
+ })
102
+ ```
103
+
104
+ ## SSR Local Fetch
105
+
106
+ When `ssrFetch` is enabled (default: `true`), the plugin patches `globalThis.fetch` so that local paths are routed in-process through Moost instead of making a real HTTP request. This is useful for SSR where server-side code fetches from its own API:
107
+
108
+ ```ts
109
+ // During SSR rendering:
110
+ const res = await fetch('/api/hello/world')
111
+ // → Routed in-process to Moost, no TCP round-trip
112
+ ```
113
+
114
+ If no Moost route matches, the call falls back to the original `fetch`. External URLs (e.g. `https://api.example.com`) always go through real HTTP.
115
+
116
+ Disable with `ssrFetch: false` when running behind Nitro or another framework that manages fetch routing itself.
117
+
118
+ ## Hot Module Replacement
119
+
120
+ Both modes support full HMR for Moost controllers:
121
+
122
+ 1. File change detected → Vite invalidates the module graph
123
+ 2. Moost DI container is cleaned up (stale instances ejected, dependants cascade)
124
+ 3. The SSR entry is re-imported → Moost re-initializes with updated controllers
125
+ 4. New requests use the updated code — no server restart needed
126
+
127
+ The plugin injects a `__VITE_ID` decorator on `@Injectable` and `@Controller` classes to track which file each class belongs to, enabling precise cleanup during hot reloads.
128
+
28
129
  ## Options
29
130
 
30
- | Option | Type | Default | Description |
31
- | ----------- | ------------------- | ------------- | --------------------------------- |
32
- | `entry` | `string` | — | Application entry file (required) |
33
- | `port` | `number` | `3000` | Dev server port |
34
- | `host` | `string` | `'localhost'` | Dev server host |
35
- | `outDir` | `string` | `'dist'` | Build output directory |
36
- | `format` | `'cjs' \| 'esm'` | `'esm'` | Output module format |
37
- | `sourcemap` | `boolean` | `true` | Generate source maps |
38
- | `externals` | `boolean \| object` | `true` | Configure external dependencies |
131
+ | Option | Type | Default | Description |
132
+ |---|---|---|---|
133
+ | `entry` | `string` | — | Application entry file (required) |
134
+ | `port` | `number` | `3000` | Dev server port (backend mode only) |
135
+ | `host` | `string` | `'localhost'` | Dev server host (backend mode only) |
136
+ | `outDir` | `string` | `'dist'` | Build output directory (backend mode only) |
137
+ | `format` | `'cjs' \| 'esm'` | `'esm'` | Output module format (backend mode only) |
138
+ | `sourcemap` | `boolean` | `true` | Generate source maps (backend mode only) |
139
+ | `externals` | `boolean \| object` | `true` | Configure external dependencies (backend mode only) |
140
+ | `onEject` | `function` | — | Hook to control DI instance ejection during HMR |
141
+ | `ssrFetch` | `boolean` | `true` | Enable local fetch interception for SSR |
142
+ | `middleware` | `boolean` | `false` | Run Moost as Connect middleware (Vite serves frontend) |
143
+ | `prefix` | `string` | — | URL prefix filter for middleware mode (e.g. `'/api'`) |
144
+
145
+ Options marked "backend mode only" are ignored when `middleware: true` — the user's `vite.config.ts` controls build/server configuration in middleware mode.
39
146
 
40
147
  ## License
41
148
 
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  }) : target, mod));
22
22
 
23
23
  //#endregion
24
+ const vite = __toESM(require("vite"));
24
25
  const magic_string = __toESM(require("magic-string"));
25
26
  const node_fs = __toESM(require("node:fs"));
26
27
  const node_module = __toESM(require("node:module"));
@@ -28,6 +29,11 @@ const moost = __toESM(require("moost"));
28
29
 
29
30
  //#region packages/vite/src/utils.ts
30
31
  const PLUGIN_NAME = "moost-vite";
32
+ const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
33
+ const DEFAULT_SSR_STATE = "<!--ssr-state-->";
34
+ function entryBasename(entry) {
35
+ return entry.split("/").pop().replace(/\.ts$/, ".js");
36
+ }
31
37
  /**
32
38
  * Recursively gathers all importer modules upstream from a given module.
33
39
  *
@@ -41,14 +47,18 @@ const PLUGIN_NAME = "moost-vite";
41
47
  if (moduleNode.importers) for (const importer of moduleNode.importers) gatherAllImporters(importer, visited);
42
48
  return visited;
43
49
  }
44
- const logger = new moost.EventLogger("", { level: 99 }).createTopic("\x1B[2m\x1B[36m" + PLUGIN_NAME);
50
+ const logger = (0, moost.createLogger)({ level: 99 }).createTopic("\x1B[2m\x1B[36m" + PLUGIN_NAME);
45
51
  function getLogger() {
46
52
  return logger;
47
53
  }
54
+ function escapeRegex(str) {
55
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
56
+ }
48
57
  function getExternals({ node, workspace }) {
49
58
  const pkg = JSON.parse((0, node_fs.readFileSync)("package.json", "utf8").toString());
50
- const externals = workspace ? Object.keys(pkg.dependencies || {}) : Object.entries(pkg.dependencies || {}).filter(([key, ver]) => !ver.startsWith("workspace:")).map((i) => i[0]);
51
- if (node) externals.push(...node_module.builtinModules, ...node_module.builtinModules.map((m) => `node:${m}`));
59
+ const packageNames = workspace ? Object.keys(pkg.dependencies || {}) : Object.entries(pkg.dependencies || {}).filter(([key, ver]) => !ver.startsWith("workspace:")).map((i) => i[0]);
60
+ const externals = packageNames.map((name) => /* @__PURE__ */ new RegExp(`^${escapeRegex(name)}(/|$)`));
61
+ if (node) externals.push(...node_module.builtinModules.map((m) => /* @__PURE__ */ new RegExp(`^${escapeRegex(m)}(/|$)`)), ...node_module.builtinModules.map((m) => /* @__PURE__ */ new RegExp(`^node:${escapeRegex(m)}(/|$)`)));
52
62
  return externals;
53
63
  }
54
64
 
@@ -72,7 +82,7 @@ function getExternals({ node, workspace }) {
72
82
  const constructorName$1 = `Moost${adapter.charAt(0).toUpperCase() + adapter.slice(1)}`;
73
83
  getLogger().log(`🔍 Extracting Adapter "${constructorName$1}"`);
74
84
  this.constructor = module$1[constructorName$1];
75
- if (onInit && this.constructor) onInit(this.constructor);
85
+ if (onInit && this.constructor) onInit(this.constructor, module$1);
76
86
  },
77
87
  compare(c) {
78
88
  if (this.detected && this.constructor) return this.constructor === c || c instanceof this.constructor || c.prototype instanceof this.constructor;
@@ -315,12 +325,29 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
315
325
  const isTest = process.env.NODE_ENV === "test";
316
326
  const isProd = process.env.NODE_ENV === "production";
317
327
  const externals = options.externals ?? true;
328
+ let prefix = options.prefix;
329
+ if (prefix) {
330
+ if (!prefix.startsWith("/")) prefix = `/${prefix}`;
331
+ if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
332
+ }
333
+ const prefixSlash = prefix ? prefix + "/" : void 0;
334
+ const prefixQuery = prefix ? prefix + "?" : void 0;
318
335
  let moostMiddleware = null;
336
+ let localFetchTeardown = null;
337
+ /** In middleware mode: maps req → next() for the onNoMatch callback */ const pendingNextMap = /* @__PURE__ */ new WeakMap();
319
338
  const adapters = isTest ? [] : [
320
- createAdapterDetector("http", (MoostHttp) => {
339
+ createAdapterDetector("http", (MoostHttp, moduleExports) => {
321
340
  MoostHttp.prototype.listen = function(...args) {
322
341
  logger$1.log(`🔌 Overtaking HTTP.listen`);
323
- moostMiddleware = this.getServerCb();
342
+ if (options.middleware) moostMiddleware = this.getServerCb((req) => {
343
+ pendingNextMap.get(req)?.();
344
+ });
345
+ else moostMiddleware = this.getServerCb();
346
+ if (options.ssrFetch !== false && moduleExports?.enableLocalFetch) {
347
+ if (localFetchTeardown) localFetchTeardown();
348
+ localFetchTeardown = moduleExports.enableLocalFetch(this);
349
+ logger$1.log(`🔀 Local fetch enabled for SSR`);
350
+ }
324
351
  setTimeout(() => {
325
352
  args.filter((a) => typeof a === "function").forEach((a) => a());
326
353
  }, 1);
@@ -337,17 +364,63 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
337
364
  name: PLUGIN_NAME,
338
365
  enforce: "pre",
339
366
  config(cfg) {
367
+ if (options.middleware) {
368
+ const defaultExternals = getExternals({
369
+ node: true,
370
+ workspace: true
371
+ });
372
+ const serverDefines = {
373
+ "process.env.MOOST_DEFERRED_ENV": "\"production\"",
374
+ __MOOST_ENTRY__: JSON.stringify(options.entry),
375
+ __MOOST_PREFIX__: JSON.stringify(prefix || "/api")
376
+ };
377
+ const environments = {
378
+ client: { build: { outDir: cfg.build?.outDir || "dist/client" } },
379
+ server: {
380
+ build: {
381
+ outDir: "dist",
382
+ emptyOutDir: false,
383
+ ssr: "server.ts",
384
+ minify: false,
385
+ sourcemap: !!(options.sourcemap ?? true),
386
+ rollupOptions: {
387
+ external: (id) => {
388
+ if (id === "@moostjs/vite/server") return false;
389
+ return defaultExternals.some((ext) => ext.test(id));
390
+ },
391
+ output: { format: "esm" }
392
+ }
393
+ },
394
+ define: serverDefines
395
+ }
396
+ };
397
+ if (options.ssrEntry) {
398
+ const ssrEntryBasename = entryBasename(options.ssrEntry);
399
+ environments.client.build.ssrManifest = true;
400
+ environments.ssr = { build: {
401
+ outDir: "dist/ssr",
402
+ ssr: options.ssrEntry
403
+ } };
404
+ serverDefines.__MOOST_SSR_ENTRY__ = JSON.stringify(`./ssr/${ssrEntryBasename}`);
405
+ serverDefines.__MOOST_SSR_OUTLET__ = JSON.stringify(options.ssrOutlet || DEFAULT_SSR_OUTLET);
406
+ serverDefines.__MOOST_SSR_STATE__ = JSON.stringify(options.ssrState || DEFAULT_SSR_STATE);
407
+ }
408
+ const envNames = Object.keys(environments);
409
+ return {
410
+ builder: { async buildApp(builder) {
411
+ for (const name of envNames) if (builder.environments[name]) await builder.build(builder.environments[name]);
412
+ } },
413
+ environments
414
+ };
415
+ }
340
416
  const entry = cfg.build?.rollupOptions?.input || options.entry;
341
- const outfile = typeof entry === "string" ? entry.split("/").pop().replace(/\.ts$/, ".js") : void 0;
417
+ const outfile = typeof entry === "string" ? entryBasename(entry) : void 0;
342
418
  return {
343
419
  server: {
344
420
  port: cfg.server?.port || options.port || 3e3,
345
421
  host: cfg.server?.host || options.host
346
422
  },
347
- optimizeDeps: {
348
- noDiscovery: cfg.optimizeDeps?.noDiscovery === void 0 ? true : cfg.optimizeDeps.noDiscovery,
349
- exclude: cfg.optimizeDeps?.exclude || ["@swc/core"]
350
- },
423
+ optimizeDeps: { noDiscovery: cfg.optimizeDeps?.noDiscovery === void 0 ? true : cfg.optimizeDeps.noDiscovery },
351
424
  build: {
352
425
  target: cfg.build?.target || "node",
353
426
  outDir: cfg.build?.outDir || options.outDir || "dist",
@@ -369,6 +442,16 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
369
442
  }
370
443
  };
371
444
  },
445
+ configResolved(config) {
446
+ config.__moostViteOptions = {
447
+ entry: options.entry,
448
+ ssrEntry: options.ssrEntry,
449
+ prefix,
450
+ port: options.port,
451
+ ssrOutlet: options.ssrOutlet,
452
+ ssrState: options.ssrState
453
+ };
454
+ },
372
455
  async transform(code, id) {
373
456
  if (!id.endsWith(".ts")) return null;
374
457
  for (const adapter of adapters) if (!adapter.detected && adapter.regex.test(code)) await adapter.init();
@@ -399,19 +482,32 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
399
482
  };
400
483
  },
401
484
  async configureServer(server) {
402
- for (const adapter of adapters) adapter.ssrLoadModule = (id) => server.ssrLoadModule(id);
485
+ const runner = (0, vite.createServerModuleRunner)(server.environments.ssr);
486
+ const ssrImport = (id) => runner.import(id);
487
+ for (const adapter of adapters) adapter.ssrLoadModule = ssrImport;
403
488
  moostRestartCleanup(adapters, options.onEject);
404
- await server.ssrLoadModule(options.entry);
489
+ await ssrImport(options.entry);
405
490
  server.middlewares.use(async (req, res, next) => {
406
491
  if (reloadRequired) {
407
492
  reloadRequired = false;
408
493
  console.log();
409
494
  logger$1.debug("🚀 Reloading Moost App...");
410
495
  console.log();
411
- await server.ssrLoadModule(options.entry);
496
+ await ssrImport(options.entry);
412
497
  await new Promise((resolve) => setTimeout(resolve, 1));
413
498
  }
414
- if (moostMiddleware) return moostMiddleware(req, res);
499
+ if (options.middleware && prefix) {
500
+ const url = req.url || "";
501
+ if (url !== prefix && !url.startsWith(prefixSlash) && !url.startsWith(prefixQuery)) return next();
502
+ }
503
+ if (moostMiddleware) {
504
+ if (options.middleware) {
505
+ pendingNextMap.set(req, next);
506
+ moostMiddleware(req, res);
507
+ return;
508
+ }
509
+ return moostMiddleware(req, res);
510
+ }
415
511
  next();
416
512
  });
417
513
  },
@@ -427,6 +523,10 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
427
523
  this.environment.moduleGraph.invalidateModule(mod);
428
524
  }
429
525
  moostMiddleware = null;
526
+ if (localFetchTeardown) {
527
+ localFetchTeardown();
528
+ localFetchTeardown = null;
529
+ }
430
530
  moostRestartCleanup(adapters, options.onEject, cleanupInstances);
431
531
  reloadRequired = true;
432
532
  }
package/dist/index.d.ts CHANGED
@@ -65,6 +65,54 @@ interface TMoostViteDevOptions {
65
65
  workspace?: boolean;
66
66
  } | boolean;
67
67
  onEject?: (instance: object, dependency: Function) => boolean;
68
+ /**
69
+ * Whether to enable local fetch interception for SSR.
70
+ * When enabled, `fetch('/path')` calls are routed to the Moost HTTP adapter
71
+ * in-process instead of making a network request.
72
+ *
73
+ * Set to `false` when running behind Nitro or another framework that
74
+ * manages fetch routing itself.
75
+ *
76
+ * Default: `true`.
77
+ */
78
+ ssrFetch?: boolean;
79
+ /**
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).
84
+ *
85
+ * Use this for fullstack apps where Vite serves the frontend (Vue, React, etc.)
86
+ * and Moost handles API routes.
87
+ *
88
+ * Default: `false`.
89
+ */
90
+ middleware?: boolean;
91
+ /**
92
+ * URL prefix for Moost API routes in middleware mode.
93
+ * Only requests starting with this prefix are dispatched to Moost.
94
+ * Ignored when `middleware` is `false`.
95
+ *
96
+ * Example: `'/api'`
97
+ */
98
+ prefix?: string;
99
+ /**
100
+ * Vue/React SSR entry module path. Used by the SSR server helper
101
+ * (`@moostjs/vite/server`) for server-side rendering.
102
+ *
103
+ * Example: `'/src/entry-server.ts'`
104
+ */
105
+ ssrEntry?: string;
106
+ /**
107
+ * HTML placeholder for SSR-rendered content.
108
+ * Default: `'<!--ssr-outlet-->'`
109
+ */
110
+ ssrOutlet?: string;
111
+ /**
112
+ * HTML placeholder for SSR state transfer script.
113
+ * Default: `'<!--ssr-state-->'`
114
+ */
115
+ ssrState?: string;
68
116
  }
69
117
  /**
70
118
  * The main Vite plugin for integrating Moost applications.
package/dist/index.mjs CHANGED
@@ -1,10 +1,16 @@
1
+ import { createServerModuleRunner } from "vite";
1
2
  import MagicString from "magic-string";
2
3
  import { readFileSync } from "node:fs";
3
4
  import { builtinModules } from "node:module";
4
- import { EventLogger, Moost, clearGlobalWooks, getMoostInfact, getMoostMate } from "moost";
5
+ import { Moost, clearGlobalWooks, createLogger, getMoostInfact, getMoostMate } from "moost";
5
6
 
6
7
  //#region packages/vite/src/utils.ts
7
8
  const PLUGIN_NAME = "moost-vite";
9
+ const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
10
+ const DEFAULT_SSR_STATE = "<!--ssr-state-->";
11
+ function entryBasename(entry) {
12
+ return entry.split("/").pop().replace(/\.ts$/, ".js");
13
+ }
8
14
  /**
9
15
  * Recursively gathers all importer modules upstream from a given module.
10
16
  *
@@ -18,14 +24,18 @@ const PLUGIN_NAME = "moost-vite";
18
24
  if (moduleNode.importers) for (const importer of moduleNode.importers) gatherAllImporters(importer, visited);
19
25
  return visited;
20
26
  }
21
- const logger = new EventLogger("", { level: 99 }).createTopic("\x1B[2m\x1B[36m" + PLUGIN_NAME);
27
+ const logger = createLogger({ level: 99 }).createTopic("\x1B[2m\x1B[36m" + PLUGIN_NAME);
22
28
  function getLogger() {
23
29
  return logger;
24
30
  }
31
+ function escapeRegex(str) {
32
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33
+ }
25
34
  function getExternals({ node, workspace }) {
26
35
  const pkg = JSON.parse(readFileSync("package.json", "utf8").toString());
27
- const externals = workspace ? Object.keys(pkg.dependencies || {}) : Object.entries(pkg.dependencies || {}).filter(([key, ver]) => !ver.startsWith("workspace:")).map((i) => i[0]);
28
- if (node) externals.push(...builtinModules, ...builtinModules.map((m) => `node:${m}`));
36
+ const packageNames = workspace ? Object.keys(pkg.dependencies || {}) : Object.entries(pkg.dependencies || {}).filter(([key, ver]) => !ver.startsWith("workspace:")).map((i) => i[0]);
37
+ const externals = packageNames.map((name) => /* @__PURE__ */ new RegExp(`^${escapeRegex(name)}(/|$)`));
38
+ if (node) externals.push(...builtinModules.map((m) => /* @__PURE__ */ new RegExp(`^${escapeRegex(m)}(/|$)`)), ...builtinModules.map((m) => /* @__PURE__ */ new RegExp(`^node:${escapeRegex(m)}(/|$)`)));
29
39
  return externals;
30
40
  }
31
41
 
@@ -49,7 +59,7 @@ function getExternals({ node, workspace }) {
49
59
  const constructorName$1 = `Moost${adapter.charAt(0).toUpperCase() + adapter.slice(1)}`;
50
60
  getLogger().log(`🔍 Extracting Adapter "${constructorName$1}"`);
51
61
  this.constructor = module[constructorName$1];
52
- if (onInit && this.constructor) onInit(this.constructor);
62
+ if (onInit && this.constructor) onInit(this.constructor, module);
53
63
  },
54
64
  compare(c) {
55
65
  if (this.detected && this.constructor) return this.constructor === c || c instanceof this.constructor || c.prototype instanceof this.constructor;
@@ -292,12 +302,29 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
292
302
  const isTest = false;
293
303
  const isProd = false;
294
304
  const externals = options.externals ?? true;
305
+ let prefix = options.prefix;
306
+ if (prefix) {
307
+ if (!prefix.startsWith("/")) prefix = `/${prefix}`;
308
+ if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
309
+ }
310
+ const prefixSlash = prefix ? prefix + "/" : void 0;
311
+ const prefixQuery = prefix ? prefix + "?" : void 0;
295
312
  let moostMiddleware = null;
313
+ let localFetchTeardown = null;
314
+ /** In middleware mode: maps req → next() for the onNoMatch callback */ const pendingNextMap = /* @__PURE__ */ new WeakMap();
296
315
  const adapters = isTest ? [] : [
297
- createAdapterDetector("http", (MoostHttp) => {
316
+ createAdapterDetector("http", (MoostHttp, moduleExports) => {
298
317
  MoostHttp.prototype.listen = function(...args) {
299
318
  logger$1.log(`🔌 Overtaking HTTP.listen`);
300
- moostMiddleware = this.getServerCb();
319
+ if (options.middleware) moostMiddleware = this.getServerCb((req) => {
320
+ pendingNextMap.get(req)?.();
321
+ });
322
+ else moostMiddleware = this.getServerCb();
323
+ if (options.ssrFetch !== false && moduleExports?.enableLocalFetch) {
324
+ if (localFetchTeardown) localFetchTeardown();
325
+ localFetchTeardown = moduleExports.enableLocalFetch(this);
326
+ logger$1.log(`🔀 Local fetch enabled for SSR`);
327
+ }
301
328
  setTimeout(() => {
302
329
  args.filter((a) => typeof a === "function").forEach((a) => a());
303
330
  }, 1);
@@ -314,17 +341,63 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
314
341
  name: PLUGIN_NAME,
315
342
  enforce: "pre",
316
343
  config(cfg) {
344
+ if (options.middleware) {
345
+ const defaultExternals = getExternals({
346
+ node: true,
347
+ workspace: true
348
+ });
349
+ const serverDefines = {
350
+ "process.env.MOOST_DEFERRED_ENV": "\"production\"",
351
+ __MOOST_ENTRY__: JSON.stringify(options.entry),
352
+ __MOOST_PREFIX__: JSON.stringify(prefix || "/api")
353
+ };
354
+ const environments = {
355
+ client: { build: { outDir: cfg.build?.outDir || "dist/client" } },
356
+ server: {
357
+ build: {
358
+ outDir: "dist",
359
+ emptyOutDir: false,
360
+ ssr: "server.ts",
361
+ minify: false,
362
+ sourcemap: !!(options.sourcemap ?? true),
363
+ rollupOptions: {
364
+ external: (id) => {
365
+ if (id === "@moostjs/vite/server") return false;
366
+ return defaultExternals.some((ext) => ext.test(id));
367
+ },
368
+ output: { format: "esm" }
369
+ }
370
+ },
371
+ define: serverDefines
372
+ }
373
+ };
374
+ if (options.ssrEntry) {
375
+ const ssrEntryBasename = entryBasename(options.ssrEntry);
376
+ environments.client.build.ssrManifest = true;
377
+ environments.ssr = { build: {
378
+ outDir: "dist/ssr",
379
+ ssr: options.ssrEntry
380
+ } };
381
+ serverDefines.__MOOST_SSR_ENTRY__ = JSON.stringify(`./ssr/${ssrEntryBasename}`);
382
+ serverDefines.__MOOST_SSR_OUTLET__ = JSON.stringify(options.ssrOutlet || DEFAULT_SSR_OUTLET);
383
+ serverDefines.__MOOST_SSR_STATE__ = JSON.stringify(options.ssrState || DEFAULT_SSR_STATE);
384
+ }
385
+ const envNames = Object.keys(environments);
386
+ return {
387
+ builder: { async buildApp(builder) {
388
+ for (const name of envNames) if (builder.environments[name]) await builder.build(builder.environments[name]);
389
+ } },
390
+ environments
391
+ };
392
+ }
317
393
  const entry = cfg.build?.rollupOptions?.input || options.entry;
318
- const outfile = typeof entry === "string" ? entry.split("/").pop().replace(/\.ts$/, ".js") : void 0;
394
+ const outfile = typeof entry === "string" ? entryBasename(entry) : void 0;
319
395
  return {
320
396
  server: {
321
397
  port: cfg.server?.port || options.port || 3e3,
322
398
  host: cfg.server?.host || options.host
323
399
  },
324
- optimizeDeps: {
325
- noDiscovery: cfg.optimizeDeps?.noDiscovery === void 0 ? true : cfg.optimizeDeps.noDiscovery,
326
- exclude: cfg.optimizeDeps?.exclude || ["@swc/core"]
327
- },
400
+ optimizeDeps: { noDiscovery: cfg.optimizeDeps?.noDiscovery === void 0 ? true : cfg.optimizeDeps.noDiscovery },
328
401
  build: {
329
402
  target: cfg.build?.target || "node",
330
403
  outDir: cfg.build?.outDir || options.outDir || "dist",
@@ -346,6 +419,16 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
346
419
  }
347
420
  };
348
421
  },
422
+ configResolved(config) {
423
+ config.__moostViteOptions = {
424
+ entry: options.entry,
425
+ ssrEntry: options.ssrEntry,
426
+ prefix,
427
+ port: options.port,
428
+ ssrOutlet: options.ssrOutlet,
429
+ ssrState: options.ssrState
430
+ };
431
+ },
349
432
  async transform(code, id) {
350
433
  if (!id.endsWith(".ts")) return null;
351
434
  for (const adapter of adapters) if (!adapter.detected && adapter.regex.test(code)) await adapter.init();
@@ -376,19 +459,32 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
376
459
  };
377
460
  },
378
461
  async configureServer(server) {
379
- for (const adapter of adapters) adapter.ssrLoadModule = (id) => server.ssrLoadModule(id);
462
+ const runner = createServerModuleRunner(server.environments.ssr);
463
+ const ssrImport = (id) => runner.import(id);
464
+ for (const adapter of adapters) adapter.ssrLoadModule = ssrImport;
380
465
  moostRestartCleanup(adapters, options.onEject);
381
- await server.ssrLoadModule(options.entry);
466
+ await ssrImport(options.entry);
382
467
  server.middlewares.use(async (req, res, next) => {
383
468
  if (reloadRequired) {
384
469
  reloadRequired = false;
385
470
  console.log();
386
471
  logger$1.debug("🚀 Reloading Moost App...");
387
472
  console.log();
388
- await server.ssrLoadModule(options.entry);
473
+ await ssrImport(options.entry);
389
474
  await new Promise((resolve) => setTimeout(resolve, 1));
390
475
  }
391
- if (moostMiddleware) return moostMiddleware(req, res);
476
+ if (options.middleware && prefix) {
477
+ const url = req.url || "";
478
+ if (url !== prefix && !url.startsWith(prefixSlash) && !url.startsWith(prefixQuery)) return next();
479
+ }
480
+ if (moostMiddleware) {
481
+ if (options.middleware) {
482
+ pendingNextMap.set(req, next);
483
+ moostMiddleware(req, res);
484
+ return;
485
+ }
486
+ return moostMiddleware(req, res);
487
+ }
392
488
  next();
393
489
  });
394
490
  },
@@ -404,6 +500,10 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
404
500
  this.environment.moduleGraph.invalidateModule(mod);
405
501
  }
406
502
  moostMiddleware = null;
503
+ if (localFetchTeardown) {
504
+ localFetchTeardown();
505
+ localFetchTeardown = null;
506
+ }
407
507
  moostRestartCleanup(adapters, options.onEject, cleanupInstances);
408
508
  reloadRequired = true;
409
509
  }
@@ -0,0 +1,177 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ require("node:fs");
25
+ require("node:module");
26
+ const moost = __toESM(require("moost"));
27
+
28
+ //#region packages/vite/src/utils.ts
29
+ const PLUGIN_NAME = "moost-vite";
30
+ const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
31
+ const DEFAULT_SSR_STATE = "<!--ssr-state-->";
32
+ const logger = (0, moost.createLogger)({ level: 99 }).createTopic("\x1B[2m\x1B[36m" + PLUGIN_NAME);
33
+
34
+ //#endregion
35
+ //#region packages/vite/src/prod-server.ts
36
+ function stripUndefined(obj) {
37
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
38
+ }
39
+ async function createSSRServer(options) {
40
+ const userMiddlewares = [];
41
+ const opts = options ? stripUndefined(options) : {};
42
+ const isProd = process.env.MOOST_DEFERRED_ENV === "production";
43
+ if (!isProd) {
44
+ const { createServer: createViteServer } = await import("vite");
45
+ const vite = await createViteServer();
46
+ const pluginOpts = vite.config.__moostViteOptions || {};
47
+ const config = {
48
+ ...pluginOpts,
49
+ ...opts
50
+ };
51
+ const ssrEntry = config.ssrEntry;
52
+ const ssrOutlet$1 = config.ssrOutlet || DEFAULT_SSR_OUTLET;
53
+ const ssrState$1 = config.ssrState || DEFAULT_SSR_STATE;
54
+ if (!ssrEntry) throw new Error("createSSRServer: ssrEntry is required. Set it in moostVite() options or pass it directly.");
55
+ const fs$1 = await import("node:fs/promises");
56
+ const path$1 = await import("node:path");
57
+ const ssrFallback = async (req, res, next) => {
58
+ if (req.method !== "GET") return next();
59
+ const url = req.originalUrl || req.url || "/";
60
+ try {
61
+ let template$1 = await fs$1.readFile(path$1.resolve(process.cwd(), "index.html"), "utf-8");
62
+ template$1 = await vite.transformIndexHtml(url, template$1);
63
+ const { render: render$1 } = await vite.ssrLoadModule(ssrEntry);
64
+ const { html: appHtml, state } = await render$1(url);
65
+ res.statusCode = 200;
66
+ res.setHeader("Content-Type", "text/html");
67
+ res.end(template$1.replace(ssrOutlet$1, appHtml).replace(ssrState$1, state ? `<script>window.__SSR_STATE__=${state}</script>` : ""));
68
+ } catch (e) {
69
+ vite.ssrFixStacktrace(e);
70
+ console.error(e);
71
+ res.statusCode = 500;
72
+ res.end(e.message);
73
+ }
74
+ };
75
+ return {
76
+ use(mw) {
77
+ userMiddlewares.push(mw);
78
+ },
79
+ async listen(_port) {
80
+ for (const mw of userMiddlewares) vite.middlewares.use(mw);
81
+ vite.middlewares.use(ssrFallback);
82
+ await vite.listen();
83
+ vite.printUrls();
84
+ }
85
+ };
86
+ }
87
+ const fs = await import("node:fs/promises");
88
+ const path = await import("node:path");
89
+ const { createServer: createHttpServer } = await import("node:http");
90
+ const clientDir = opts.clientDir || "dist/client";
91
+ const ssrOutlet = opts.ssrOutlet || (typeof __MOOST_SSR_OUTLET__ !== "undefined" ? __MOOST_SSR_OUTLET__ : "<!--ssr-outlet-->");
92
+ const ssrState = opts.ssrState || (typeof __MOOST_SSR_STATE__ !== "undefined" ? __MOOST_SSR_STATE__ : "<!--ssr-state-->");
93
+ let prefix = opts.prefix || __MOOST_PREFIX__;
94
+ if (!prefix.startsWith("/")) prefix = `/${prefix}`;
95
+ if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
96
+ const prefixSlash = prefix + "/";
97
+ const defaultPort = opts.port || Number(process.env.PORT) || 3e3;
98
+ const template = await fs.readFile(path.resolve(clientDir, "index.html"), "utf-8");
99
+ let render = null;
100
+ const hasSsr = typeof __MOOST_SSR_ENTRY__ !== "undefined" && !!__MOOST_SSR_ENTRY__;
101
+ if (hasSsr) {
102
+ const ssrModule = await import(
103
+ /* @vite-ignore */
104
+ __MOOST_SSR_ENTRY__);
105
+ render = ssrModule.render;
106
+ }
107
+ const { MoostHttp, enableLocalFetch } = await import("@moostjs/event-http");
108
+ let moostHandler = null;
109
+ let moostHttpInstance = null;
110
+ const origListen = MoostHttp.prototype.listen;
111
+ MoostHttp.prototype.listen = function(...args) {
112
+ moostHandler = this.getServerCb();
113
+ moostHttpInstance = this;
114
+ setTimeout(() => args.filter((a) => typeof a === "function").forEach((a) => a()), 1);
115
+ return Promise.resolve();
116
+ };
117
+ if (typeof opts.entry === "function") await opts.entry();
118
+ else if (typeof opts.entry === "string") await import(
119
+ /* @vite-ignore */
120
+ opts.entry);
121
+ else await import(__MOOST_ENTRY__);
122
+ MoostHttp.prototype.listen = origListen;
123
+ if (enableLocalFetch && moostHttpInstance) enableLocalFetch(moostHttpInstance);
124
+ const sirv = (await import("sirv")).default;
125
+ const serve = sirv(path.resolve(clientDir), { extensions: [] });
126
+ return {
127
+ use(mw) {
128
+ userMiddlewares.push(mw);
129
+ },
130
+ async listen(port) {
131
+ const p = port || defaultPort;
132
+ createHttpServer((req, res) => {
133
+ const url = req.url || "/";
134
+ let i = 0;
135
+ const runNext = () => {
136
+ if (i < userMiddlewares.length) {
137
+ userMiddlewares[i++](req, res, runNext);
138
+ return;
139
+ }
140
+ if (moostHandler && (url.startsWith(prefixSlash) || url === prefix || url.startsWith(prefix + "?"))) {
141
+ moostHandler(req, res);
142
+ return;
143
+ }
144
+ serve(req, res, async () => {
145
+ if (req.method !== "GET") {
146
+ res.statusCode = 404;
147
+ res.end();
148
+ return;
149
+ }
150
+ try {
151
+ if (render) {
152
+ const { html: appHtml, state } = await render(url);
153
+ res.statusCode = 200;
154
+ res.setHeader("Content-Type", "text/html");
155
+ res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}</script>` : ""));
156
+ } else {
157
+ res.statusCode = 200;
158
+ res.setHeader("Content-Type", "text/html");
159
+ res.end(template);
160
+ }
161
+ } catch (e) {
162
+ console.error(e);
163
+ res.statusCode = 500;
164
+ res.end(e.message);
165
+ }
166
+ });
167
+ };
168
+ runNext();
169
+ }).listen(p, () => {
170
+ console.log(`Server running at http://localhost:${p}`);
171
+ });
172
+ }
173
+ };
174
+ }
175
+
176
+ //#endregion
177
+ exports.createSSRServer = createSSRServer;
@@ -0,0 +1,29 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
+
3
+ type TMiddleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
4
+ interface TSSRServerOptions {
5
+ /** Override: lazy import for the Moost app entry. */
6
+ entry?: (() => Promise<unknown>) | string;
7
+ /** Override: SSR entry module path (e.g., '/src/entry-server.ts') */
8
+ ssrEntry?: string;
9
+ /** Override: URL prefix for API routes (e.g., '/api') */
10
+ prefix?: string;
11
+ /** Override: port number (default: process.env.PORT || 3000) */
12
+ port?: number;
13
+ /** Override: client build directory (default: 'dist/client') */
14
+ clientDir?: string;
15
+ /** Override: HTML placeholder for SSR content */
16
+ ssrOutlet?: string;
17
+ /** Override: HTML placeholder for SSR state */
18
+ ssrState?: string;
19
+ }
20
+ interface TSSRServer {
21
+ /** Add Connect-compatible middleware (runs in both dev and prod) */
22
+ use(middleware: TMiddleware): void;
23
+ /** Start the server */
24
+ listen(port?: number): Promise<void>;
25
+ }
26
+ declare function createSSRServer(options?: TSSRServerOptions): Promise<TSSRServer>;
27
+
28
+ export { createSSRServer };
29
+ export type { TSSRServer, TSSRServerOptions };
@@ -0,0 +1,154 @@
1
+ import "node:fs";
2
+ import "node:module";
3
+ import { createLogger } from "moost";
4
+
5
+ //#region packages/vite/src/utils.ts
6
+ const PLUGIN_NAME = "moost-vite";
7
+ const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
8
+ const DEFAULT_SSR_STATE = "<!--ssr-state-->";
9
+ const logger = createLogger({ level: 99 }).createTopic("\x1B[2m\x1B[36m" + PLUGIN_NAME);
10
+
11
+ //#endregion
12
+ //#region packages/vite/src/prod-server.ts
13
+ function stripUndefined(obj) {
14
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
15
+ }
16
+ async function createSSRServer(options) {
17
+ const userMiddlewares = [];
18
+ const opts = options ? stripUndefined(options) : {};
19
+ const isProd = process.env.MOOST_DEFERRED_ENV === "production";
20
+ if (!isProd) {
21
+ const { createServer: createViteServer } = await import("vite");
22
+ const vite = await createViteServer();
23
+ const pluginOpts = vite.config.__moostViteOptions || {};
24
+ const config = {
25
+ ...pluginOpts,
26
+ ...opts
27
+ };
28
+ const ssrEntry = config.ssrEntry;
29
+ const ssrOutlet$1 = config.ssrOutlet || DEFAULT_SSR_OUTLET;
30
+ const ssrState$1 = config.ssrState || DEFAULT_SSR_STATE;
31
+ if (!ssrEntry) throw new Error("createSSRServer: ssrEntry is required. Set it in moostVite() options or pass it directly.");
32
+ const fs$1 = await import("node:fs/promises");
33
+ const path$1 = await import("node:path");
34
+ const ssrFallback = async (req, res, next) => {
35
+ if (req.method !== "GET") return next();
36
+ const url = req.originalUrl || req.url || "/";
37
+ try {
38
+ let template$1 = await fs$1.readFile(path$1.resolve(process.cwd(), "index.html"), "utf-8");
39
+ template$1 = await vite.transformIndexHtml(url, template$1);
40
+ const { render: render$1 } = await vite.ssrLoadModule(ssrEntry);
41
+ const { html: appHtml, state } = await render$1(url);
42
+ res.statusCode = 200;
43
+ res.setHeader("Content-Type", "text/html");
44
+ res.end(template$1.replace(ssrOutlet$1, appHtml).replace(ssrState$1, state ? `<script>window.__SSR_STATE__=${state}</script>` : ""));
45
+ } catch (e) {
46
+ vite.ssrFixStacktrace(e);
47
+ console.error(e);
48
+ res.statusCode = 500;
49
+ res.end(e.message);
50
+ }
51
+ };
52
+ return {
53
+ use(mw) {
54
+ userMiddlewares.push(mw);
55
+ },
56
+ async listen(_port) {
57
+ for (const mw of userMiddlewares) vite.middlewares.use(mw);
58
+ vite.middlewares.use(ssrFallback);
59
+ await vite.listen();
60
+ vite.printUrls();
61
+ }
62
+ };
63
+ }
64
+ const fs = await import("node:fs/promises");
65
+ const path = await import("node:path");
66
+ const { createServer: createHttpServer } = await import("node:http");
67
+ const clientDir = opts.clientDir || "dist/client";
68
+ const ssrOutlet = opts.ssrOutlet || (typeof __MOOST_SSR_OUTLET__ !== "undefined" ? __MOOST_SSR_OUTLET__ : "<!--ssr-outlet-->");
69
+ const ssrState = opts.ssrState || (typeof __MOOST_SSR_STATE__ !== "undefined" ? __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 + "/";
74
+ const defaultPort = opts.port || Number(process.env.PORT) || 3e3;
75
+ const template = await fs.readFile(path.resolve(clientDir, "index.html"), "utf-8");
76
+ let render = null;
77
+ const hasSsr = typeof __MOOST_SSR_ENTRY__ !== "undefined" && !!__MOOST_SSR_ENTRY__;
78
+ if (hasSsr) {
79
+ const ssrModule = await import(
80
+ /* @vite-ignore */
81
+ __MOOST_SSR_ENTRY__);
82
+ render = ssrModule.render;
83
+ }
84
+ const { MoostHttp, enableLocalFetch } = await import("@moostjs/event-http");
85
+ let moostHandler = null;
86
+ let moostHttpInstance = null;
87
+ const origListen = MoostHttp.prototype.listen;
88
+ MoostHttp.prototype.listen = function(...args) {
89
+ moostHandler = this.getServerCb();
90
+ moostHttpInstance = this;
91
+ setTimeout(() => args.filter((a) => typeof a === "function").forEach((a) => a()), 1);
92
+ return Promise.resolve();
93
+ };
94
+ if (typeof opts.entry === "function") await opts.entry();
95
+ else if (typeof opts.entry === "string") await import(
96
+ /* @vite-ignore */
97
+ opts.entry);
98
+ else await import(__MOOST_ENTRY__);
99
+ MoostHttp.prototype.listen = origListen;
100
+ if (enableLocalFetch && moostHttpInstance) enableLocalFetch(moostHttpInstance);
101
+ const sirv = (await import("sirv")).default;
102
+ const serve = sirv(path.resolve(clientDir), { extensions: [] });
103
+ return {
104
+ use(mw) {
105
+ userMiddlewares.push(mw);
106
+ },
107
+ async listen(port) {
108
+ const p = port || defaultPort;
109
+ createHttpServer((req, res) => {
110
+ const url = req.url || "/";
111
+ let i = 0;
112
+ const runNext = () => {
113
+ if (i < userMiddlewares.length) {
114
+ userMiddlewares[i++](req, res, runNext);
115
+ return;
116
+ }
117
+ if (moostHandler && (url.startsWith(prefixSlash) || url === prefix || url.startsWith(prefix + "?"))) {
118
+ moostHandler(req, res);
119
+ return;
120
+ }
121
+ serve(req, res, async () => {
122
+ if (req.method !== "GET") {
123
+ res.statusCode = 404;
124
+ res.end();
125
+ return;
126
+ }
127
+ try {
128
+ if (render) {
129
+ const { html: appHtml, state } = await render(url);
130
+ res.statusCode = 200;
131
+ res.setHeader("Content-Type", "text/html");
132
+ res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}</script>` : ""));
133
+ } else {
134
+ res.statusCode = 200;
135
+ res.setHeader("Content-Type", "text/html");
136
+ res.end(template);
137
+ }
138
+ } catch (e) {
139
+ console.error(e);
140
+ res.statusCode = 500;
141
+ res.end(e.message);
142
+ }
143
+ });
144
+ };
145
+ runNext();
146
+ }).listen(p, () => {
147
+ console.log(`Server running at http://localhost:${p}`);
148
+ });
149
+ }
150
+ };
151
+ }
152
+
153
+ //#endregion
154
+ export { createSSRServer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moostjs/vite",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Vite Dev plugin for moostjs",
5
5
  "keywords": [
6
6
  "composables",
@@ -36,8 +36,19 @@
36
36
  "types": "./dist/index.d.ts",
37
37
  "import": "./dist/index.mjs",
38
38
  "require": "./dist/index.cjs"
39
+ },
40
+ "./server": {
41
+ "types": "./dist/prod-server.d.ts",
42
+ "import": "./dist/prod-server.mjs",
43
+ "require": "./dist/prod-server.cjs"
39
44
  }
40
45
  },
46
+ "build": {
47
+ "entries": [
48
+ "src/index.ts",
49
+ "src/prod-server.ts"
50
+ ]
51
+ },
41
52
  "dependencies": {
42
53
  "magic-string": "^0.30.21"
43
54
  },
@@ -45,9 +56,15 @@
45
56
  "vitest": "3.2.4"
46
57
  },
47
58
  "peerDependencies": {
48
- "vite": "^7.0.0",
49
- "@moostjs/event-http": "^0.6.1",
50
- "moost": "^0.6.1"
59
+ "sirv": "^3.0.0",
60
+ "vite": "^8.0.0",
61
+ "@moostjs/event-http": "^0.6.3",
62
+ "moost": "^0.6.3"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "sirv": {
66
+ "optional": true
67
+ }
51
68
  },
52
69
  "scripts": {
53
70
  "pub": "pnpm publish --access public",