@moostjs/vite 0.6.2 → 0.6.4
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 +170 -11
- package/dist/index.cjs +165 -18
- package/dist/index.d.ts +55 -19
- package/dist/index.mjs +165 -18
- package/dist/prod-server.cjs +179 -0
- package/dist/prod-server.d.ts +29 -0
- package/dist/prod-server.mjs +156 -0
- package/package.json +21 -4
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
|
-
##
|
|
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,176 @@ 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 Mode
|
|
105
|
+
|
|
106
|
+
Add `ssrEntry` to enable server-side rendering. The plugin handles everything automatically — SSR rendering in dev, multi-environment builds for production:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
moostVite({
|
|
110
|
+
entry: '/src/main.ts',
|
|
111
|
+
middleware: true,
|
|
112
|
+
prefix: '/api',
|
|
113
|
+
ssrEntry: '/src/entry-server.ts',
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
In dev, the plugin adds an SSR fallback middleware that renders pages on the server using your `entry-server.ts`. In production, `vite build` produces three bundles in a single pass:
|
|
118
|
+
|
|
119
|
+
- **client** — optimized browser assets (`dist/client/`)
|
|
120
|
+
- **ssr** — server-side render function (`dist/server/ssr/`)
|
|
121
|
+
- **server** — production Node.js server (`dist/server/server.js`)
|
|
122
|
+
|
|
123
|
+
### SPA Mode
|
|
124
|
+
|
|
125
|
+
Omit `ssrEntry` and Vite serves the app as a standard SPA. The production build still generates a server that serves static files and API routes — it just skips server-side rendering.
|
|
126
|
+
|
|
127
|
+
### Custom Server Entry
|
|
128
|
+
|
|
129
|
+
By default, `vite build` auto-generates a minimal production server. If you need custom middleware (compression, auth, logging), provide your own server file:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
moostVite({
|
|
133
|
+
entry: '/src/main.ts',
|
|
134
|
+
middleware: true,
|
|
135
|
+
prefix: '/api',
|
|
136
|
+
ssrEntry: '/src/entry-server.ts',
|
|
137
|
+
serverEntry: './server.ts',
|
|
138
|
+
})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
// server.ts
|
|
143
|
+
import { createSSRServer } from '@moostjs/vite/server'
|
|
144
|
+
|
|
145
|
+
const app = await createSSRServer()
|
|
146
|
+
// app.use(compression())
|
|
147
|
+
await app.listen()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`createSSRServer` handles dev/prod branching — in dev it creates a Vite dev server, in prod it serves built assets with `sirv`.
|
|
151
|
+
|
|
152
|
+
## SSR Local Fetch
|
|
153
|
+
|
|
154
|
+
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:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
// During SSR rendering:
|
|
158
|
+
const res = await fetch('/api/hello/world')
|
|
159
|
+
// → Routed in-process to Moost, no TCP round-trip
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
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.
|
|
163
|
+
|
|
164
|
+
Disable with `ssrFetch: false` when running behind Nitro or another framework that manages fetch routing itself.
|
|
165
|
+
|
|
166
|
+
## Hot Module Replacement
|
|
167
|
+
|
|
168
|
+
Both modes support full HMR for Moost controllers:
|
|
169
|
+
|
|
170
|
+
1. File change detected → Vite invalidates the module graph
|
|
171
|
+
2. Moost DI container is cleaned up (stale instances ejected, dependants cascade)
|
|
172
|
+
3. The SSR entry is re-imported → Moost re-initializes with updated controllers
|
|
173
|
+
4. New requests use the updated code — no server restart needed
|
|
174
|
+
|
|
175
|
+
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.
|
|
176
|
+
|
|
28
177
|
## Options
|
|
29
178
|
|
|
30
|
-
| Option
|
|
31
|
-
|
|
32
|
-
| `entry`
|
|
33
|
-
| `port`
|
|
34
|
-
| `host`
|
|
35
|
-
| `outDir`
|
|
36
|
-
| `format`
|
|
37
|
-
| `sourcemap` | `boolean`
|
|
38
|
-
| `externals` | `boolean \| object` | `true`
|
|
179
|
+
| Option | Type | Default | Description |
|
|
180
|
+
|---|---|---|---|
|
|
181
|
+
| `entry` | `string` | — | Application entry file (required) |
|
|
182
|
+
| `port` | `number` | `3000` | Dev server port (backend mode only) |
|
|
183
|
+
| `host` | `string` | `'localhost'` | Dev server host (backend mode only) |
|
|
184
|
+
| `outDir` | `string` | `'dist'` | Build output directory (backend mode only) |
|
|
185
|
+
| `format` | `'cjs' \| 'esm'` | `'esm'` | Output module format (backend mode only) |
|
|
186
|
+
| `sourcemap` | `boolean` | `true` | Generate source maps (backend mode only) |
|
|
187
|
+
| `externals` | `boolean \| object` | `true` | Configure external dependencies (backend mode only) |
|
|
188
|
+
| `onEject` | `function` | — | Hook to control DI instance ejection during HMR |
|
|
189
|
+
| `ssrFetch` | `boolean` | `true` | Enable local fetch interception for SSR |
|
|
190
|
+
| `middleware` | `boolean` | `false` | Run Moost as Connect middleware (Vite serves frontend) |
|
|
191
|
+
| `prefix` | `string` | — | URL prefix filter for middleware mode (e.g. `'/api'`) |
|
|
192
|
+
| `ssrEntry` | `string` | — | Vue/React SSR entry module (e.g. `'/src/entry-server.ts'`) |
|
|
193
|
+
| `ssrOutlet` | `string` | `'<!--ssr-outlet-->'` | HTML placeholder for SSR-rendered content |
|
|
194
|
+
| `ssrState` | `string` | `'<!--ssr-state-->'` | HTML placeholder for SSR state transfer script |
|
|
195
|
+
| `serverEntry` | `string` | — | Custom production server entry file (e.g. `'./server.ts'`) |
|
|
196
|
+
|
|
197
|
+
Options marked "backend mode only" are ignored when `middleware: true` — the user's `vite.config.ts` controls build/server configuration in middleware mode.
|
|
39
198
|
|
|
40
199
|
## License
|
|
41
200
|
|
package/dist/index.cjs
CHANGED
|
@@ -21,13 +21,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
}) : target, mod));
|
|
22
22
|
|
|
23
23
|
//#endregion
|
|
24
|
-
const magic_string = __toESM(require("magic-string"));
|
|
25
24
|
const node_fs = __toESM(require("node:fs"));
|
|
25
|
+
const node_path = __toESM(require("node:path"));
|
|
26
|
+
const vite = __toESM(require("vite"));
|
|
27
|
+
const magic_string = __toESM(require("magic-string"));
|
|
26
28
|
const node_module = __toESM(require("node:module"));
|
|
27
29
|
const moost = __toESM(require("moost"));
|
|
28
30
|
|
|
29
31
|
//#region packages/vite/src/utils.ts
|
|
30
32
|
const PLUGIN_NAME = "moost-vite";
|
|
33
|
+
const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
|
|
34
|
+
const DEFAULT_SSR_STATE = "<!--ssr-state-->";
|
|
35
|
+
function entryBasename(entry) {
|
|
36
|
+
return entry.split("/").pop().replace(/\.ts$/, ".js");
|
|
37
|
+
}
|
|
31
38
|
/**
|
|
32
39
|
* Recursively gathers all importer modules upstream from a given module.
|
|
33
40
|
*
|
|
@@ -45,10 +52,14 @@ const logger = (0, moost.createLogger)({ level: 99 }).createTopic("\x1B[2m\x1B[3
|
|
|
45
52
|
function getLogger() {
|
|
46
53
|
return logger;
|
|
47
54
|
}
|
|
55
|
+
function escapeRegex(str) {
|
|
56
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
57
|
+
}
|
|
48
58
|
function getExternals({ node, workspace }) {
|
|
49
59
|
const pkg = JSON.parse((0, node_fs.readFileSync)("package.json", "utf8").toString());
|
|
50
|
-
const
|
|
51
|
-
|
|
60
|
+
const packageNames = workspace ? Object.keys(pkg.dependencies || {}) : Object.entries(pkg.dependencies || {}).filter(([key, ver]) => !ver.startsWith("workspace:")).map((i) => i[0]);
|
|
61
|
+
const externals = packageNames.map((name) => /* @__PURE__ */ new RegExp(`^${escapeRegex(name)}(/|$)`));
|
|
62
|
+
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
63
|
return externals;
|
|
53
64
|
}
|
|
54
65
|
|
|
@@ -72,7 +83,7 @@ function getExternals({ node, workspace }) {
|
|
|
72
83
|
const constructorName$1 = `Moost${adapter.charAt(0).toUpperCase() + adapter.slice(1)}`;
|
|
73
84
|
getLogger().log(`🔍 [2mExtracting Adapter "${constructorName$1}"`);
|
|
74
85
|
this.constructor = module$1[constructorName$1];
|
|
75
|
-
if (onInit && this.constructor) onInit(this.constructor);
|
|
86
|
+
if (onInit && this.constructor) onInit(this.constructor, module$1);
|
|
76
87
|
},
|
|
77
88
|
compare(c) {
|
|
78
89
|
if (this.detected && this.constructor) return this.constructor === c || c instanceof this.constructor || c.prototype instanceof this.constructor;
|
|
@@ -311,16 +322,44 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
|
|
|
311
322
|
*
|
|
312
323
|
* @param {TMoostViteDevOptions} options - Configuration options for the Moost Vite plugin.
|
|
313
324
|
* @returns {PluginOption} The configured Vite plugin.
|
|
314
|
-
*/
|
|
325
|
+
*/ const DEFAULT_SERVER_ENTRY_CODE = `import { createSSRServer } from '@moostjs/vite/server'
|
|
326
|
+
const app = await createSSRServer()
|
|
327
|
+
await app.listen()
|
|
328
|
+
`;
|
|
329
|
+
function generatedServerEntry(root) {
|
|
330
|
+
const dir = (0, node_path.resolve)(root || process.cwd(), "node_modules", ".moost-vite");
|
|
331
|
+
(0, node_fs.mkdirSync)(dir, { recursive: true });
|
|
332
|
+
const entryPath = (0, node_path.resolve)(dir, "server-entry.mjs");
|
|
333
|
+
(0, node_fs.writeFileSync)(entryPath, DEFAULT_SERVER_ENTRY_CODE);
|
|
334
|
+
return entryPath;
|
|
335
|
+
}
|
|
336
|
+
function moostVite(options) {
|
|
315
337
|
const isTest = process.env.NODE_ENV === "test";
|
|
316
338
|
const isProd = process.env.NODE_ENV === "production";
|
|
317
339
|
const externals = options.externals ?? true;
|
|
340
|
+
let prefix = options.prefix;
|
|
341
|
+
if (prefix) {
|
|
342
|
+
if (!prefix.startsWith("/")) prefix = `/${prefix}`;
|
|
343
|
+
if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
|
|
344
|
+
}
|
|
345
|
+
const prefixSlash = prefix ? prefix + "/" : void 0;
|
|
346
|
+
const prefixQuery = prefix ? prefix + "?" : void 0;
|
|
318
347
|
let moostMiddleware = null;
|
|
348
|
+
let localFetchTeardown = null;
|
|
349
|
+
/** In middleware mode: maps req → next() for the onNoMatch callback */ const pendingNextMap = /* @__PURE__ */ new WeakMap();
|
|
319
350
|
const adapters = isTest ? [] : [
|
|
320
|
-
createAdapterDetector("http", (MoostHttp) => {
|
|
351
|
+
createAdapterDetector("http", (MoostHttp, moduleExports) => {
|
|
321
352
|
MoostHttp.prototype.listen = function(...args) {
|
|
322
353
|
logger$1.log(`🔌 [2mOvertaking HTTP.listen`);
|
|
323
|
-
moostMiddleware = this.getServerCb()
|
|
354
|
+
if (options.middleware) moostMiddleware = this.getServerCb((req) => {
|
|
355
|
+
pendingNextMap.get(req)?.();
|
|
356
|
+
});
|
|
357
|
+
else moostMiddleware = this.getServerCb();
|
|
358
|
+
if (options.ssrFetch !== false && moduleExports?.enableLocalFetch) {
|
|
359
|
+
if (localFetchTeardown) localFetchTeardown();
|
|
360
|
+
localFetchTeardown = moduleExports.enableLocalFetch(this);
|
|
361
|
+
logger$1.log(`🔀 [2mLocal fetch enabled for SSR`);
|
|
362
|
+
}
|
|
324
363
|
setTimeout(() => {
|
|
325
364
|
args.filter((a) => typeof a === "function").forEach((a) => a());
|
|
326
365
|
}, 1);
|
|
@@ -337,17 +376,69 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
|
|
|
337
376
|
name: PLUGIN_NAME,
|
|
338
377
|
enforce: "pre",
|
|
339
378
|
config(cfg) {
|
|
379
|
+
if (options.middleware) {
|
|
380
|
+
const defaultExternals = getExternals({
|
|
381
|
+
node: true,
|
|
382
|
+
workspace: true
|
|
383
|
+
});
|
|
384
|
+
const serverDefines = {
|
|
385
|
+
"process.env.MOOST_DEFERRED_ENV": "\"production\"",
|
|
386
|
+
__MOOST_ENTRY__: JSON.stringify(options.entry),
|
|
387
|
+
__MOOST_PREFIX__: JSON.stringify(prefix || "/api")
|
|
388
|
+
};
|
|
389
|
+
const serverEntry = options.serverEntry || generatedServerEntry(cfg.root);
|
|
390
|
+
const ssrInput = { server: serverEntry };
|
|
391
|
+
if (options.ssrEntry) {
|
|
392
|
+
const ssrBasename = entryBasename(options.ssrEntry);
|
|
393
|
+
ssrInput[`ssr/${ssrBasename.replace(/\.js$/, "")}`] = options.ssrEntry;
|
|
394
|
+
serverDefines.__MOOST_SSR_ENTRY__ = JSON.stringify(`./ssr/${ssrBasename}`);
|
|
395
|
+
serverDefines.__MOOST_SSR_OUTLET__ = JSON.stringify(options.ssrOutlet || DEFAULT_SSR_OUTLET);
|
|
396
|
+
serverDefines.__MOOST_SSR_STATE__ = JSON.stringify(options.ssrState || DEFAULT_SSR_STATE);
|
|
397
|
+
}
|
|
398
|
+
const outDir = cfg.build?.outDir || "dist";
|
|
399
|
+
return {
|
|
400
|
+
...options.ssrEntry && { appType: "custom" },
|
|
401
|
+
builder: { async buildApp(builder) {
|
|
402
|
+
(0, node_fs.rmSync)((0, node_path.resolve)(cfg.root || process.cwd(), outDir), {
|
|
403
|
+
recursive: true,
|
|
404
|
+
force: true
|
|
405
|
+
});
|
|
406
|
+
for (const env of Object.values(builder.environments)) await builder.build(env);
|
|
407
|
+
} },
|
|
408
|
+
environments: {
|
|
409
|
+
client: { build: {
|
|
410
|
+
outDir: `${outDir}/client`,
|
|
411
|
+
emptyOutDir: false,
|
|
412
|
+
...options.ssrEntry && { ssrManifest: true }
|
|
413
|
+
} },
|
|
414
|
+
ssr: {
|
|
415
|
+
build: {
|
|
416
|
+
outDir: `${outDir}/server`,
|
|
417
|
+
emptyOutDir: false,
|
|
418
|
+
minify: false,
|
|
419
|
+
sourcemap: !!(options.sourcemap ?? true),
|
|
420
|
+
rollupOptions: {
|
|
421
|
+
input: ssrInput,
|
|
422
|
+
external: (id) => {
|
|
423
|
+
if (id === "@moostjs/vite/server") return false;
|
|
424
|
+
return defaultExternals.some((ext) => ext.test(id));
|
|
425
|
+
},
|
|
426
|
+
output: { format: "esm" }
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
define: serverDefines
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
340
434
|
const entry = cfg.build?.rollupOptions?.input || options.entry;
|
|
341
|
-
const outfile = typeof entry === "string" ? entry
|
|
435
|
+
const outfile = typeof entry === "string" ? entryBasename(entry) : void 0;
|
|
342
436
|
return {
|
|
343
437
|
server: {
|
|
344
438
|
port: cfg.server?.port || options.port || 3e3,
|
|
345
439
|
host: cfg.server?.host || options.host
|
|
346
440
|
},
|
|
347
|
-
optimizeDeps: {
|
|
348
|
-
noDiscovery: cfg.optimizeDeps?.noDiscovery === void 0 ? true : cfg.optimizeDeps.noDiscovery,
|
|
349
|
-
exclude: cfg.optimizeDeps?.exclude || ["@swc/core"]
|
|
350
|
-
},
|
|
441
|
+
optimizeDeps: { noDiscovery: cfg.optimizeDeps?.noDiscovery === void 0 ? true : cfg.optimizeDeps.noDiscovery },
|
|
351
442
|
build: {
|
|
352
443
|
target: cfg.build?.target || "node",
|
|
353
444
|
outDir: cfg.build?.outDir || options.outDir || "dist",
|
|
@@ -369,6 +460,17 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
|
|
|
369
460
|
}
|
|
370
461
|
};
|
|
371
462
|
},
|
|
463
|
+
configResolved(config) {
|
|
464
|
+
config.__moostViteOptions = {
|
|
465
|
+
entry: options.entry,
|
|
466
|
+
ssrEntry: options.ssrEntry,
|
|
467
|
+
serverEntry: options.serverEntry,
|
|
468
|
+
prefix,
|
|
469
|
+
port: options.port,
|
|
470
|
+
ssrOutlet: options.ssrOutlet,
|
|
471
|
+
ssrState: options.ssrState
|
|
472
|
+
};
|
|
473
|
+
},
|
|
372
474
|
async transform(code, id) {
|
|
373
475
|
if (!id.endsWith(".ts")) return null;
|
|
374
476
|
for (const adapter of adapters) if (!adapter.detected && adapter.regex.test(code)) await adapter.init();
|
|
@@ -399,19 +501,32 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
|
|
|
399
501
|
};
|
|
400
502
|
},
|
|
401
503
|
async configureServer(server) {
|
|
402
|
-
|
|
504
|
+
const runner = (0, vite.createServerModuleRunner)(server.environments.ssr);
|
|
505
|
+
const ssrImport = (id) => runner.import(id);
|
|
506
|
+
for (const adapter of adapters) adapter.ssrLoadModule = ssrImport;
|
|
403
507
|
moostRestartCleanup(adapters, options.onEject);
|
|
404
|
-
await
|
|
508
|
+
await ssrImport(options.entry);
|
|
405
509
|
server.middlewares.use(async (req, res, next) => {
|
|
406
510
|
if (reloadRequired) {
|
|
407
511
|
reloadRequired = false;
|
|
408
512
|
console.log();
|
|
409
513
|
logger$1.debug("🚀 Reloading Moost App...");
|
|
410
514
|
console.log();
|
|
411
|
-
await
|
|
412
|
-
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
515
|
+
await ssrImport(options.entry);
|
|
516
|
+
await new Promise((resolve$1) => setTimeout(resolve$1, 1));
|
|
517
|
+
}
|
|
518
|
+
if (options.middleware && prefix) {
|
|
519
|
+
const url = req.url || "";
|
|
520
|
+
if (url !== prefix && !url.startsWith(prefixSlash) && !url.startsWith(prefixQuery)) return next();
|
|
521
|
+
}
|
|
522
|
+
if (moostMiddleware) {
|
|
523
|
+
if (options.middleware) {
|
|
524
|
+
pendingNextMap.set(req, next);
|
|
525
|
+
moostMiddleware(req, res);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
return moostMiddleware(req, res);
|
|
413
529
|
}
|
|
414
|
-
if (moostMiddleware) return moostMiddleware(req, res);
|
|
415
530
|
next();
|
|
416
531
|
});
|
|
417
532
|
},
|
|
@@ -427,6 +542,10 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
|
|
|
427
542
|
this.environment.moduleGraph.invalidateModule(mod);
|
|
428
543
|
}
|
|
429
544
|
moostMiddleware = null;
|
|
545
|
+
if (localFetchTeardown) {
|
|
546
|
+
localFetchTeardown();
|
|
547
|
+
localFetchTeardown = null;
|
|
548
|
+
}
|
|
430
549
|
moostRestartCleanup(adapters, options.onEject, cleanupInstances);
|
|
431
550
|
reloadRequired = true;
|
|
432
551
|
}
|
|
@@ -441,7 +560,35 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
|
|
|
441
560
|
delete pluginConfig.transform;
|
|
442
561
|
delete pluginConfig.hotUpdate;
|
|
443
562
|
}
|
|
444
|
-
|
|
563
|
+
const ssrFallbackPlugin = !isProd && !isTest && options.middleware && options.ssrEntry ? {
|
|
564
|
+
name: `${PLUGIN_NAME}:ssr-fallback`,
|
|
565
|
+
async configureServer(server) {
|
|
566
|
+
const ssrOutlet = options.ssrOutlet || DEFAULT_SSR_OUTLET;
|
|
567
|
+
const ssrState = options.ssrState || DEFAULT_SSR_STATE;
|
|
568
|
+
const fs = await import("node:fs/promises");
|
|
569
|
+
return () => {
|
|
570
|
+
server.middlewares.use(async (req, res, next) => {
|
|
571
|
+
if (req.method !== "GET") return next();
|
|
572
|
+
const url = req.originalUrl || req.url || "/";
|
|
573
|
+
try {
|
|
574
|
+
let template = await fs.readFile((0, node_path.resolve)(process.cwd(), "index.html"), "utf-8");
|
|
575
|
+
template = await server.transformIndexHtml(url, template);
|
|
576
|
+
const { render } = await server.ssrLoadModule(options.ssrEntry);
|
|
577
|
+
const { html: appHtml, state } = await render(url);
|
|
578
|
+
res.statusCode = 200;
|
|
579
|
+
res.setHeader("Content-Type", "text/html");
|
|
580
|
+
res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}</script>` : ""));
|
|
581
|
+
} catch (e) {
|
|
582
|
+
server.ssrFixStacktrace(e);
|
|
583
|
+
console.error(e);
|
|
584
|
+
res.statusCode = 500;
|
|
585
|
+
res.end(e.message);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
} : null;
|
|
591
|
+
return [pluginConfig, ssrFallbackPlugin];
|
|
445
592
|
}
|
|
446
593
|
|
|
447
594
|
//#endregion
|
package/dist/index.d.ts
CHANGED
|
@@ -65,26 +65,62 @@ 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;
|
|
116
|
+
/**
|
|
117
|
+
* Path to a custom server entry file (e.g., `'./server.ts'`).
|
|
118
|
+
* When provided, this file is used as the production server build entry.
|
|
119
|
+
* When omitted in middleware mode, the plugin auto-generates a minimal
|
|
120
|
+
* production server that serves static files + Moost API (+ SSR if configured).
|
|
121
|
+
*/
|
|
122
|
+
serverEntry?: string;
|
|
68
123
|
}
|
|
69
|
-
/**
|
|
70
|
-
* The main Vite plugin for integrating Moost applications.
|
|
71
|
-
*
|
|
72
|
-
* Features:
|
|
73
|
-
* - **Adapter Detection**: Detects Moost adapter usage (`http`, `cli`, `wf`) and applies relevant configurations.
|
|
74
|
-
* - **Dev Mode Middleware**:
|
|
75
|
-
* - Patches `MoostHttp.prototype.listen` to register a custom middleware for serving the app via Vite's dev server instead of binding to a port.
|
|
76
|
-
* - Handles Moost state cleanup and hot module replacement (HMR) during development.
|
|
77
|
-
* - **Class Tracking**: Injects a `__VITE_ID()` decorator into exported classes to enable tracking and cleanup during hot reloads.
|
|
78
|
-
* - **Externals Support**:
|
|
79
|
-
* - Allows marking Node.js built-in modules and dependencies from `package.json` (optionally excluding workspace dependencies) as external during builds.
|
|
80
|
-
* - Configured via the `externals` option, which helps reduce bundle size and ensures compatibility with runtime environments.
|
|
81
|
-
* - **Build and Test Friendly**:
|
|
82
|
-
* - Avoids interfering with Vitest runs by skipping dev-specific behaviors during tests.
|
|
83
|
-
* - Ensures proper SSR entry setup and Rollup configurations for production builds.
|
|
84
|
-
*
|
|
85
|
-
* @param {TMoostViteDevOptions} options - Configuration options for the Moost Vite plugin.
|
|
86
|
-
* @returns {PluginOption} The configured Vite plugin.
|
|
87
|
-
*/
|
|
88
124
|
declare function moostVite(options: TMoostViteDevOptions): PluginOption;
|
|
89
125
|
|
|
90
126
|
export { moostVite };
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { createServerModuleRunner } from "vite";
|
|
1
4
|
import MagicString from "magic-string";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
5
|
import { builtinModules } from "node:module";
|
|
4
6
|
import { Moost, clearGlobalWooks, createLogger, getMoostInfact, getMoostMate } from "moost";
|
|
5
7
|
|
|
6
8
|
//#region packages/vite/src/utils.ts
|
|
7
9
|
const PLUGIN_NAME = "moost-vite";
|
|
10
|
+
const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
|
|
11
|
+
const DEFAULT_SSR_STATE = "<!--ssr-state-->";
|
|
12
|
+
function entryBasename(entry) {
|
|
13
|
+
return entry.split("/").pop().replace(/\.ts$/, ".js");
|
|
14
|
+
}
|
|
8
15
|
/**
|
|
9
16
|
* Recursively gathers all importer modules upstream from a given module.
|
|
10
17
|
*
|
|
@@ -22,10 +29,14 @@ const logger = createLogger({ level: 99 }).createTopic("\x1B[2m\x1B[36m" + PLUGI
|
|
|
22
29
|
function getLogger() {
|
|
23
30
|
return logger;
|
|
24
31
|
}
|
|
32
|
+
function escapeRegex(str) {
|
|
33
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
34
|
+
}
|
|
25
35
|
function getExternals({ node, workspace }) {
|
|
26
36
|
const pkg = JSON.parse(readFileSync("package.json", "utf8").toString());
|
|
27
|
-
const
|
|
28
|
-
|
|
37
|
+
const packageNames = workspace ? Object.keys(pkg.dependencies || {}) : Object.entries(pkg.dependencies || {}).filter(([key, ver]) => !ver.startsWith("workspace:")).map((i) => i[0]);
|
|
38
|
+
const externals = packageNames.map((name) => /* @__PURE__ */ new RegExp(`^${escapeRegex(name)}(/|$)`));
|
|
39
|
+
if (node) externals.push(...builtinModules.map((m) => /* @__PURE__ */ new RegExp(`^${escapeRegex(m)}(/|$)`)), ...builtinModules.map((m) => /* @__PURE__ */ new RegExp(`^node:${escapeRegex(m)}(/|$)`)));
|
|
29
40
|
return externals;
|
|
30
41
|
}
|
|
31
42
|
|
|
@@ -49,7 +60,7 @@ function getExternals({ node, workspace }) {
|
|
|
49
60
|
const constructorName$1 = `Moost${adapter.charAt(0).toUpperCase() + adapter.slice(1)}`;
|
|
50
61
|
getLogger().log(`🔍 [2mExtracting Adapter "${constructorName$1}"`);
|
|
51
62
|
this.constructor = module[constructorName$1];
|
|
52
|
-
if (onInit && this.constructor) onInit(this.constructor);
|
|
63
|
+
if (onInit && this.constructor) onInit(this.constructor, module);
|
|
53
64
|
},
|
|
54
65
|
compare(c) {
|
|
55
66
|
if (this.detected && this.constructor) return this.constructor === c || c instanceof this.constructor || c.prototype instanceof this.constructor;
|
|
@@ -288,16 +299,44 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
|
|
|
288
299
|
*
|
|
289
300
|
* @param {TMoostViteDevOptions} options - Configuration options for the Moost Vite plugin.
|
|
290
301
|
* @returns {PluginOption} The configured Vite plugin.
|
|
291
|
-
*/
|
|
302
|
+
*/ const DEFAULT_SERVER_ENTRY_CODE = `import { createSSRServer } from '@moostjs/vite/server'
|
|
303
|
+
const app = await createSSRServer()
|
|
304
|
+
await app.listen()
|
|
305
|
+
`;
|
|
306
|
+
function generatedServerEntry(root) {
|
|
307
|
+
const dir = resolve(root || process.cwd(), "node_modules", ".moost-vite");
|
|
308
|
+
mkdirSync(dir, { recursive: true });
|
|
309
|
+
const entryPath = resolve(dir, "server-entry.mjs");
|
|
310
|
+
writeFileSync(entryPath, DEFAULT_SERVER_ENTRY_CODE);
|
|
311
|
+
return entryPath;
|
|
312
|
+
}
|
|
313
|
+
function moostVite(options) {
|
|
292
314
|
const isTest = false;
|
|
293
315
|
const isProd = false;
|
|
294
316
|
const externals = options.externals ?? true;
|
|
317
|
+
let prefix = options.prefix;
|
|
318
|
+
if (prefix) {
|
|
319
|
+
if (!prefix.startsWith("/")) prefix = `/${prefix}`;
|
|
320
|
+
if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
|
|
321
|
+
}
|
|
322
|
+
const prefixSlash = prefix ? prefix + "/" : void 0;
|
|
323
|
+
const prefixQuery = prefix ? prefix + "?" : void 0;
|
|
295
324
|
let moostMiddleware = null;
|
|
325
|
+
let localFetchTeardown = null;
|
|
326
|
+
/** In middleware mode: maps req → next() for the onNoMatch callback */ const pendingNextMap = /* @__PURE__ */ new WeakMap();
|
|
296
327
|
const adapters = isTest ? [] : [
|
|
297
|
-
createAdapterDetector("http", (MoostHttp) => {
|
|
328
|
+
createAdapterDetector("http", (MoostHttp, moduleExports) => {
|
|
298
329
|
MoostHttp.prototype.listen = function(...args) {
|
|
299
330
|
logger$1.log(`🔌 [2mOvertaking HTTP.listen`);
|
|
300
|
-
moostMiddleware = this.getServerCb()
|
|
331
|
+
if (options.middleware) moostMiddleware = this.getServerCb((req) => {
|
|
332
|
+
pendingNextMap.get(req)?.();
|
|
333
|
+
});
|
|
334
|
+
else moostMiddleware = this.getServerCb();
|
|
335
|
+
if (options.ssrFetch !== false && moduleExports?.enableLocalFetch) {
|
|
336
|
+
if (localFetchTeardown) localFetchTeardown();
|
|
337
|
+
localFetchTeardown = moduleExports.enableLocalFetch(this);
|
|
338
|
+
logger$1.log(`🔀 [2mLocal fetch enabled for SSR`);
|
|
339
|
+
}
|
|
301
340
|
setTimeout(() => {
|
|
302
341
|
args.filter((a) => typeof a === "function").forEach((a) => a());
|
|
303
342
|
}, 1);
|
|
@@ -314,17 +353,69 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
|
|
|
314
353
|
name: PLUGIN_NAME,
|
|
315
354
|
enforce: "pre",
|
|
316
355
|
config(cfg) {
|
|
356
|
+
if (options.middleware) {
|
|
357
|
+
const defaultExternals = getExternals({
|
|
358
|
+
node: true,
|
|
359
|
+
workspace: true
|
|
360
|
+
});
|
|
361
|
+
const serverDefines = {
|
|
362
|
+
"process.env.MOOST_DEFERRED_ENV": "\"production\"",
|
|
363
|
+
__MOOST_ENTRY__: JSON.stringify(options.entry),
|
|
364
|
+
__MOOST_PREFIX__: JSON.stringify(prefix || "/api")
|
|
365
|
+
};
|
|
366
|
+
const serverEntry = options.serverEntry || generatedServerEntry(cfg.root);
|
|
367
|
+
const ssrInput = { server: serverEntry };
|
|
368
|
+
if (options.ssrEntry) {
|
|
369
|
+
const ssrBasename = entryBasename(options.ssrEntry);
|
|
370
|
+
ssrInput[`ssr/${ssrBasename.replace(/\.js$/, "")}`] = options.ssrEntry;
|
|
371
|
+
serverDefines.__MOOST_SSR_ENTRY__ = JSON.stringify(`./ssr/${ssrBasename}`);
|
|
372
|
+
serverDefines.__MOOST_SSR_OUTLET__ = JSON.stringify(options.ssrOutlet || DEFAULT_SSR_OUTLET);
|
|
373
|
+
serverDefines.__MOOST_SSR_STATE__ = JSON.stringify(options.ssrState || DEFAULT_SSR_STATE);
|
|
374
|
+
}
|
|
375
|
+
const outDir = cfg.build?.outDir || "dist";
|
|
376
|
+
return {
|
|
377
|
+
...options.ssrEntry && { appType: "custom" },
|
|
378
|
+
builder: { async buildApp(builder) {
|
|
379
|
+
rmSync(resolve(cfg.root || process.cwd(), outDir), {
|
|
380
|
+
recursive: true,
|
|
381
|
+
force: true
|
|
382
|
+
});
|
|
383
|
+
for (const env of Object.values(builder.environments)) await builder.build(env);
|
|
384
|
+
} },
|
|
385
|
+
environments: {
|
|
386
|
+
client: { build: {
|
|
387
|
+
outDir: `${outDir}/client`,
|
|
388
|
+
emptyOutDir: false,
|
|
389
|
+
...options.ssrEntry && { ssrManifest: true }
|
|
390
|
+
} },
|
|
391
|
+
ssr: {
|
|
392
|
+
build: {
|
|
393
|
+
outDir: `${outDir}/server`,
|
|
394
|
+
emptyOutDir: false,
|
|
395
|
+
minify: false,
|
|
396
|
+
sourcemap: !!(options.sourcemap ?? true),
|
|
397
|
+
rollupOptions: {
|
|
398
|
+
input: ssrInput,
|
|
399
|
+
external: (id) => {
|
|
400
|
+
if (id === "@moostjs/vite/server") return false;
|
|
401
|
+
return defaultExternals.some((ext) => ext.test(id));
|
|
402
|
+
},
|
|
403
|
+
output: { format: "esm" }
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
define: serverDefines
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
317
411
|
const entry = cfg.build?.rollupOptions?.input || options.entry;
|
|
318
|
-
const outfile = typeof entry === "string" ? entry
|
|
412
|
+
const outfile = typeof entry === "string" ? entryBasename(entry) : void 0;
|
|
319
413
|
return {
|
|
320
414
|
server: {
|
|
321
415
|
port: cfg.server?.port || options.port || 3e3,
|
|
322
416
|
host: cfg.server?.host || options.host
|
|
323
417
|
},
|
|
324
|
-
optimizeDeps: {
|
|
325
|
-
noDiscovery: cfg.optimizeDeps?.noDiscovery === void 0 ? true : cfg.optimizeDeps.noDiscovery,
|
|
326
|
-
exclude: cfg.optimizeDeps?.exclude || ["@swc/core"]
|
|
327
|
-
},
|
|
418
|
+
optimizeDeps: { noDiscovery: cfg.optimizeDeps?.noDiscovery === void 0 ? true : cfg.optimizeDeps.noDiscovery },
|
|
328
419
|
build: {
|
|
329
420
|
target: cfg.build?.target || "node",
|
|
330
421
|
outDir: cfg.build?.outDir || options.outDir || "dist",
|
|
@@ -346,6 +437,17 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
|
|
|
346
437
|
}
|
|
347
438
|
};
|
|
348
439
|
},
|
|
440
|
+
configResolved(config) {
|
|
441
|
+
config.__moostViteOptions = {
|
|
442
|
+
entry: options.entry,
|
|
443
|
+
ssrEntry: options.ssrEntry,
|
|
444
|
+
serverEntry: options.serverEntry,
|
|
445
|
+
prefix,
|
|
446
|
+
port: options.port,
|
|
447
|
+
ssrOutlet: options.ssrOutlet,
|
|
448
|
+
ssrState: options.ssrState
|
|
449
|
+
};
|
|
450
|
+
},
|
|
349
451
|
async transform(code, id) {
|
|
350
452
|
if (!id.endsWith(".ts")) return null;
|
|
351
453
|
for (const adapter of adapters) if (!adapter.detected && adapter.regex.test(code)) await adapter.init();
|
|
@@ -376,19 +478,32 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
|
|
|
376
478
|
};
|
|
377
479
|
},
|
|
378
480
|
async configureServer(server) {
|
|
379
|
-
|
|
481
|
+
const runner = createServerModuleRunner(server.environments.ssr);
|
|
482
|
+
const ssrImport = (id) => runner.import(id);
|
|
483
|
+
for (const adapter of adapters) adapter.ssrLoadModule = ssrImport;
|
|
380
484
|
moostRestartCleanup(adapters, options.onEject);
|
|
381
|
-
await
|
|
485
|
+
await ssrImport(options.entry);
|
|
382
486
|
server.middlewares.use(async (req, res, next) => {
|
|
383
487
|
if (reloadRequired) {
|
|
384
488
|
reloadRequired = false;
|
|
385
489
|
console.log();
|
|
386
490
|
logger$1.debug("🚀 Reloading Moost App...");
|
|
387
491
|
console.log();
|
|
388
|
-
await
|
|
389
|
-
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
492
|
+
await ssrImport(options.entry);
|
|
493
|
+
await new Promise((resolve$1) => setTimeout(resolve$1, 1));
|
|
494
|
+
}
|
|
495
|
+
if (options.middleware && prefix) {
|
|
496
|
+
const url = req.url || "";
|
|
497
|
+
if (url !== prefix && !url.startsWith(prefixSlash) && !url.startsWith(prefixQuery)) return next();
|
|
498
|
+
}
|
|
499
|
+
if (moostMiddleware) {
|
|
500
|
+
if (options.middleware) {
|
|
501
|
+
pendingNextMap.set(req, next);
|
|
502
|
+
moostMiddleware(req, res);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
return moostMiddleware(req, res);
|
|
390
506
|
}
|
|
391
|
-
if (moostMiddleware) return moostMiddleware(req, res);
|
|
392
507
|
next();
|
|
393
508
|
});
|
|
394
509
|
},
|
|
@@ -404,6 +519,10 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
|
|
|
404
519
|
this.environment.moduleGraph.invalidateModule(mod);
|
|
405
520
|
}
|
|
406
521
|
moostMiddleware = null;
|
|
522
|
+
if (localFetchTeardown) {
|
|
523
|
+
localFetchTeardown();
|
|
524
|
+
localFetchTeardown = null;
|
|
525
|
+
}
|
|
407
526
|
moostRestartCleanup(adapters, options.onEject, cleanupInstances);
|
|
408
527
|
reloadRequired = true;
|
|
409
528
|
}
|
|
@@ -418,7 +537,35 @@ const REG_REPLACE_EXPORT_CLASS = /(^\s*@(Injectable|Controller)\()/gm;
|
|
|
418
537
|
delete pluginConfig.transform;
|
|
419
538
|
delete pluginConfig.hotUpdate;
|
|
420
539
|
}
|
|
421
|
-
|
|
540
|
+
const ssrFallbackPlugin = !isProd && !isTest && options.middleware && options.ssrEntry ? {
|
|
541
|
+
name: `${PLUGIN_NAME}:ssr-fallback`,
|
|
542
|
+
async configureServer(server) {
|
|
543
|
+
const ssrOutlet = options.ssrOutlet || DEFAULT_SSR_OUTLET;
|
|
544
|
+
const ssrState = options.ssrState || DEFAULT_SSR_STATE;
|
|
545
|
+
const fs = await import("node:fs/promises");
|
|
546
|
+
return () => {
|
|
547
|
+
server.middlewares.use(async (req, res, next) => {
|
|
548
|
+
if (req.method !== "GET") return next();
|
|
549
|
+
const url = req.originalUrl || req.url || "/";
|
|
550
|
+
try {
|
|
551
|
+
let template = await fs.readFile(resolve(process.cwd(), "index.html"), "utf-8");
|
|
552
|
+
template = await server.transformIndexHtml(url, template);
|
|
553
|
+
const { render } = await server.ssrLoadModule(options.ssrEntry);
|
|
554
|
+
const { html: appHtml, state } = await render(url);
|
|
555
|
+
res.statusCode = 200;
|
|
556
|
+
res.setHeader("Content-Type", "text/html");
|
|
557
|
+
res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}</script>` : ""));
|
|
558
|
+
} catch (e) {
|
|
559
|
+
server.ssrFixStacktrace(e);
|
|
560
|
+
console.error(e);
|
|
561
|
+
res.statusCode = 500;
|
|
562
|
+
res.end(e.message);
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
} : null;
|
|
568
|
+
return [pluginConfig, ssrFallbackPlugin];
|
|
422
569
|
}
|
|
423
570
|
|
|
424
571
|
//#endregion
|
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
let ssrFallback = null;
|
|
55
|
+
if (ssrEntry) {
|
|
56
|
+
const fs$1 = await import("node:fs/promises");
|
|
57
|
+
const path$1 = await import("node:path");
|
|
58
|
+
ssrFallback = async (req, res, next) => {
|
|
59
|
+
if (req.method !== "GET") return next();
|
|
60
|
+
const url = req.originalUrl || req.url || "/";
|
|
61
|
+
try {
|
|
62
|
+
let template$1 = await fs$1.readFile(path$1.resolve(process.cwd(), "index.html"), "utf-8");
|
|
63
|
+
template$1 = await vite.transformIndexHtml(url, template$1);
|
|
64
|
+
const { render: render$1 } = await vite.ssrLoadModule(ssrEntry);
|
|
65
|
+
const { html: appHtml, state } = await render$1(url);
|
|
66
|
+
res.statusCode = 200;
|
|
67
|
+
res.setHeader("Content-Type", "text/html");
|
|
68
|
+
res.end(template$1.replace(ssrOutlet$1, appHtml).replace(ssrState$1, state ? `<script>window.__SSR_STATE__=${state}</script>` : ""));
|
|
69
|
+
} catch (e) {
|
|
70
|
+
vite.ssrFixStacktrace(e);
|
|
71
|
+
console.error(e);
|
|
72
|
+
res.statusCode = 500;
|
|
73
|
+
res.end(e.message);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
use(mw) {
|
|
79
|
+
userMiddlewares.push(mw);
|
|
80
|
+
},
|
|
81
|
+
async listen(_port) {
|
|
82
|
+
for (const mw of userMiddlewares) vite.middlewares.use(mw);
|
|
83
|
+
if (ssrFallback) vite.middlewares.use(ssrFallback);
|
|
84
|
+
await vite.listen();
|
|
85
|
+
vite.printUrls();
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const fs = await import("node:fs/promises");
|
|
90
|
+
const path = await import("node:path");
|
|
91
|
+
const { createServer: createHttpServer } = await import("node:http");
|
|
92
|
+
const clientDir = opts.clientDir || path.resolve(__dirname, "../client");
|
|
93
|
+
const ssrOutlet = opts.ssrOutlet || (typeof __MOOST_SSR_OUTLET__ !== "undefined" ? __MOOST_SSR_OUTLET__ : "<!--ssr-outlet-->");
|
|
94
|
+
const ssrState = opts.ssrState || (typeof __MOOST_SSR_STATE__ !== "undefined" ? __MOOST_SSR_STATE__ : "<!--ssr-state-->");
|
|
95
|
+
let prefix = opts.prefix || __MOOST_PREFIX__;
|
|
96
|
+
if (!prefix.startsWith("/")) prefix = `/${prefix}`;
|
|
97
|
+
if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
|
|
98
|
+
const prefixSlash = prefix + "/";
|
|
99
|
+
const defaultPort = opts.port || Number(process.env.PORT) || 3e3;
|
|
100
|
+
const template = await fs.readFile(path.resolve(clientDir, "index.html"), "utf-8");
|
|
101
|
+
let render = null;
|
|
102
|
+
const hasSsr = typeof __MOOST_SSR_ENTRY__ !== "undefined" && !!__MOOST_SSR_ENTRY__;
|
|
103
|
+
if (hasSsr) {
|
|
104
|
+
const ssrModule = await import(
|
|
105
|
+
/* @vite-ignore */
|
|
106
|
+
__MOOST_SSR_ENTRY__);
|
|
107
|
+
render = ssrModule.render;
|
|
108
|
+
}
|
|
109
|
+
const { MoostHttp, enableLocalFetch } = await import("@moostjs/event-http");
|
|
110
|
+
let moostHandler = null;
|
|
111
|
+
let moostHttpInstance = null;
|
|
112
|
+
const origListen = MoostHttp.prototype.listen;
|
|
113
|
+
MoostHttp.prototype.listen = function(...args) {
|
|
114
|
+
moostHandler = this.getServerCb();
|
|
115
|
+
moostHttpInstance = this;
|
|
116
|
+
setTimeout(() => args.filter((a) => typeof a === "function").forEach((a) => a()), 1);
|
|
117
|
+
return Promise.resolve();
|
|
118
|
+
};
|
|
119
|
+
if (typeof opts.entry === "function") await opts.entry();
|
|
120
|
+
else if (typeof opts.entry === "string") await import(
|
|
121
|
+
/* @vite-ignore */
|
|
122
|
+
opts.entry);
|
|
123
|
+
else await import(__MOOST_ENTRY__);
|
|
124
|
+
MoostHttp.prototype.listen = origListen;
|
|
125
|
+
if (enableLocalFetch && moostHttpInstance) enableLocalFetch(moostHttpInstance);
|
|
126
|
+
const sirv = (await import("sirv")).default;
|
|
127
|
+
const serve = sirv(path.resolve(clientDir), { extensions: [] });
|
|
128
|
+
return {
|
|
129
|
+
use(mw) {
|
|
130
|
+
userMiddlewares.push(mw);
|
|
131
|
+
},
|
|
132
|
+
async listen(port) {
|
|
133
|
+
const p = port || defaultPort;
|
|
134
|
+
createHttpServer((req, res) => {
|
|
135
|
+
const url = req.url || "/";
|
|
136
|
+
let i = 0;
|
|
137
|
+
const runNext = () => {
|
|
138
|
+
if (i < userMiddlewares.length) {
|
|
139
|
+
userMiddlewares[i++](req, res, runNext);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (moostHandler && (url.startsWith(prefixSlash) || url === prefix || url.startsWith(prefix + "?"))) {
|
|
143
|
+
moostHandler(req, res);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
serve(req, res, async () => {
|
|
147
|
+
if (req.method !== "GET") {
|
|
148
|
+
res.statusCode = 404;
|
|
149
|
+
res.end();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
if (render) {
|
|
154
|
+
const { html: appHtml, state } = await render(url);
|
|
155
|
+
res.statusCode = 200;
|
|
156
|
+
res.setHeader("Content-Type", "text/html");
|
|
157
|
+
res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}</script>` : ""));
|
|
158
|
+
} else {
|
|
159
|
+
res.statusCode = 200;
|
|
160
|
+
res.setHeader("Content-Type", "text/html");
|
|
161
|
+
res.end(template);
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {
|
|
164
|
+
console.error(e);
|
|
165
|
+
res.statusCode = 500;
|
|
166
|
+
res.end(e.message);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
runNext();
|
|
171
|
+
}).listen(p, () => {
|
|
172
|
+
console.log(`Server running at http://localhost:${p}`);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
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,156 @@
|
|
|
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
|
+
let ssrFallback = null;
|
|
32
|
+
if (ssrEntry) {
|
|
33
|
+
const fs$1 = await import("node:fs/promises");
|
|
34
|
+
const path$1 = await import("node:path");
|
|
35
|
+
ssrFallback = async (req, res, next) => {
|
|
36
|
+
if (req.method !== "GET") return next();
|
|
37
|
+
const url = req.originalUrl || req.url || "/";
|
|
38
|
+
try {
|
|
39
|
+
let template$1 = await fs$1.readFile(path$1.resolve(process.cwd(), "index.html"), "utf-8");
|
|
40
|
+
template$1 = await vite.transformIndexHtml(url, template$1);
|
|
41
|
+
const { render: render$1 } = await vite.ssrLoadModule(ssrEntry);
|
|
42
|
+
const { html: appHtml, state } = await render$1(url);
|
|
43
|
+
res.statusCode = 200;
|
|
44
|
+
res.setHeader("Content-Type", "text/html");
|
|
45
|
+
res.end(template$1.replace(ssrOutlet$1, appHtml).replace(ssrState$1, state ? `<script>window.__SSR_STATE__=${state}</script>` : ""));
|
|
46
|
+
} catch (e) {
|
|
47
|
+
vite.ssrFixStacktrace(e);
|
|
48
|
+
console.error(e);
|
|
49
|
+
res.statusCode = 500;
|
|
50
|
+
res.end(e.message);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
use(mw) {
|
|
56
|
+
userMiddlewares.push(mw);
|
|
57
|
+
},
|
|
58
|
+
async listen(_port) {
|
|
59
|
+
for (const mw of userMiddlewares) vite.middlewares.use(mw);
|
|
60
|
+
if (ssrFallback) vite.middlewares.use(ssrFallback);
|
|
61
|
+
await vite.listen();
|
|
62
|
+
vite.printUrls();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const fs = await import("node:fs/promises");
|
|
67
|
+
const path = await import("node:path");
|
|
68
|
+
const { createServer: createHttpServer } = await import("node:http");
|
|
69
|
+
const clientDir = opts.clientDir || path.resolve(import.meta.dirname, "../client");
|
|
70
|
+
const ssrOutlet = opts.ssrOutlet || (typeof __MOOST_SSR_OUTLET__ !== "undefined" ? __MOOST_SSR_OUTLET__ : "<!--ssr-outlet-->");
|
|
71
|
+
const ssrState = opts.ssrState || (typeof __MOOST_SSR_STATE__ !== "undefined" ? __MOOST_SSR_STATE__ : "<!--ssr-state-->");
|
|
72
|
+
let prefix = opts.prefix || __MOOST_PREFIX__;
|
|
73
|
+
if (!prefix.startsWith("/")) prefix = `/${prefix}`;
|
|
74
|
+
if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
|
|
75
|
+
const prefixSlash = prefix + "/";
|
|
76
|
+
const defaultPort = opts.port || Number(process.env.PORT) || 3e3;
|
|
77
|
+
const template = await fs.readFile(path.resolve(clientDir, "index.html"), "utf-8");
|
|
78
|
+
let render = null;
|
|
79
|
+
const hasSsr = typeof __MOOST_SSR_ENTRY__ !== "undefined" && !!__MOOST_SSR_ENTRY__;
|
|
80
|
+
if (hasSsr) {
|
|
81
|
+
const ssrModule = await import(
|
|
82
|
+
/* @vite-ignore */
|
|
83
|
+
__MOOST_SSR_ENTRY__);
|
|
84
|
+
render = ssrModule.render;
|
|
85
|
+
}
|
|
86
|
+
const { MoostHttp, enableLocalFetch } = await import("@moostjs/event-http");
|
|
87
|
+
let moostHandler = null;
|
|
88
|
+
let moostHttpInstance = null;
|
|
89
|
+
const origListen = MoostHttp.prototype.listen;
|
|
90
|
+
MoostHttp.prototype.listen = function(...args) {
|
|
91
|
+
moostHandler = this.getServerCb();
|
|
92
|
+
moostHttpInstance = this;
|
|
93
|
+
setTimeout(() => args.filter((a) => typeof a === "function").forEach((a) => a()), 1);
|
|
94
|
+
return Promise.resolve();
|
|
95
|
+
};
|
|
96
|
+
if (typeof opts.entry === "function") await opts.entry();
|
|
97
|
+
else if (typeof opts.entry === "string") await import(
|
|
98
|
+
/* @vite-ignore */
|
|
99
|
+
opts.entry);
|
|
100
|
+
else await import(__MOOST_ENTRY__);
|
|
101
|
+
MoostHttp.prototype.listen = origListen;
|
|
102
|
+
if (enableLocalFetch && moostHttpInstance) enableLocalFetch(moostHttpInstance);
|
|
103
|
+
const sirv = (await import("sirv")).default;
|
|
104
|
+
const serve = sirv(path.resolve(clientDir), { extensions: [] });
|
|
105
|
+
return {
|
|
106
|
+
use(mw) {
|
|
107
|
+
userMiddlewares.push(mw);
|
|
108
|
+
},
|
|
109
|
+
async listen(port) {
|
|
110
|
+
const p = port || defaultPort;
|
|
111
|
+
createHttpServer((req, res) => {
|
|
112
|
+
const url = req.url || "/";
|
|
113
|
+
let i = 0;
|
|
114
|
+
const runNext = () => {
|
|
115
|
+
if (i < userMiddlewares.length) {
|
|
116
|
+
userMiddlewares[i++](req, res, runNext);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (moostHandler && (url.startsWith(prefixSlash) || url === prefix || url.startsWith(prefix + "?"))) {
|
|
120
|
+
moostHandler(req, res);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
serve(req, res, async () => {
|
|
124
|
+
if (req.method !== "GET") {
|
|
125
|
+
res.statusCode = 404;
|
|
126
|
+
res.end();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
if (render) {
|
|
131
|
+
const { html: appHtml, state } = await render(url);
|
|
132
|
+
res.statusCode = 200;
|
|
133
|
+
res.setHeader("Content-Type", "text/html");
|
|
134
|
+
res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}</script>` : ""));
|
|
135
|
+
} else {
|
|
136
|
+
res.statusCode = 200;
|
|
137
|
+
res.setHeader("Content-Type", "text/html");
|
|
138
|
+
res.end(template);
|
|
139
|
+
}
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error(e);
|
|
142
|
+
res.statusCode = 500;
|
|
143
|
+
res.end(e.message);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
runNext();
|
|
148
|
+
}).listen(p, () => {
|
|
149
|
+
console.log(`Server running at http://localhost:${p}`);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
export { createSSRServer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moostjs/vite",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
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
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
59
|
+
"sirv": "^3.0.0",
|
|
60
|
+
"vite": "^8.0.0",
|
|
61
|
+
"@moostjs/event-http": "^0.6.4",
|
|
62
|
+
"moost": "^0.6.4"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"sirv": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
51
68
|
},
|
|
52
69
|
"scripts": {
|
|
53
70
|
"pub": "pnpm publish --access public",
|