@vercube/vite 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -73,13 +73,14 @@ node dist/index.mjs # runs the built server
|
|
|
73
73
|
|
|
74
74
|
## Options
|
|
75
75
|
|
|
76
|
-
| Option | Type | Default | Description
|
|
77
|
-
| ------------ | ---------------------- | --------------- |
|
|
78
|
-
| `rootDir` | `string` | Vite `root` | Project root used to resolve `scanDirs` and `setupFile`.
|
|
79
|
-
| `scanDirs` | `string[]` | `['src']` | Directories scanned (recursively) for `@Controller` and `@Injectable` classes.
|
|
80
|
-
| `setupFile` | `string` | `undefined` | Module whose default export `(app: App) => void \| Promise<void>` runs as `createApp`'s setup hook (before init).
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
76
|
+
| Option | Type | Default | Description |
|
|
77
|
+
| ------------ | ---------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
78
|
+
| `rootDir` | `string` | Vite `root` | Project root used to resolve `scanDirs` and `setupFile`. |
|
|
79
|
+
| `scanDirs` | `string[]` | `['src']` | Directories scanned (recursively) for `@Controller` and `@Injectable` classes. |
|
|
80
|
+
| `setupFile` | `string` | `undefined` | Module whose default export `(app: App) => void \| Promise<void>` runs as `createApp`'s setup hook (before init). |
|
|
81
|
+
| `appConfig` | `ConfigTypes.Config` | `undefined` | App config passed to `createApp({ cfg })` in the generated entry, so it ships with the build instead of a `vercube.config` read at runtime. Plain data only (written as JSON). |
|
|
82
|
+
| `runner` | `string` | `'node-worker'` | The `env-runner` runner used to execute server code in dev. |
|
|
83
|
+
| `noExternal` | `(string \| RegExp)[]` | `undefined` | Extra package patterns merged after `@vercube/*` for dev and production bundling (see below). |
|
|
83
84
|
|
|
84
85
|
### `noExternal`
|
|
85
86
|
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { dirname, isAbsolute, resolve } from "pathe";
|
|
2
|
-
import {
|
|
1
|
+
import { dirname, isAbsolute, join, resolve } from "pathe";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
3
4
|
import { scanSource } from "@vercube/scan";
|
|
4
5
|
//#region src/entry.ts
|
|
5
6
|
/**
|
|
6
7
|
* Generates the source of the server entry module.
|
|
7
8
|
*
|
|
8
|
-
* The module creates a Vercube app
|
|
9
|
-
* service into its DI container,
|
|
10
|
-
* container queue (which registers
|
|
11
|
-
* exports the app's `fetch` handler.
|
|
12
|
-
* name.
|
|
9
|
+
* The module creates a Vercube app (with the plugin's `appConfig`, if any),
|
|
10
|
+
* binds every auto-discovered controller and service into its DI container,
|
|
11
|
+
* runs the optional setup file, flushes the container queue (which registers
|
|
12
|
+
* routes via decorator initialization), and exports the app's `fetch` handler.
|
|
13
|
+
* Imports and binds are deduplicated by class name.
|
|
13
14
|
*
|
|
14
15
|
* The entry is written to a real file rather than served as a `\0`-virtual
|
|
15
16
|
* module: Vite resolves bare imports from virtual modules inconsistently with
|
|
@@ -31,7 +32,9 @@ function generateServerEntry(ctx) {
|
|
|
31
32
|
const setupBody = [];
|
|
32
33
|
if (ctx.setupFile) setupBody.push(" await __vercubeSetup__(app);");
|
|
33
34
|
if (ctx.hasClient) setupBody.push(" if (import.meta.main) {", " const dir = fileURLToPath(new URL('./public', import.meta.url));", " app.container.get(HttpServer).addPlugin(serveStaticFiles(dir));", " }");
|
|
34
|
-
|
|
35
|
+
const cfg = ctx.pluginConfig.appConfig ? `cfg: ${JSON.stringify(ctx.pluginConfig.appConfig)}` : void 0;
|
|
36
|
+
if (setupBody.length > 0) lines.push(`const app = await createApp({ ${cfg ? `${cfg}, ` : ""}setup: async (app) => {`, ...setupBody, "} });", "");
|
|
37
|
+
else if (cfg) lines.push(`const app = await createApp({ ${cfg} });`, "");
|
|
35
38
|
else lines.push("const app = await createApp();", "");
|
|
36
39
|
lines.push(...[...imports.keys()].map((name) => `app.container.bind(${name});`), "");
|
|
37
40
|
lines.push("app.container.flushQueue();", "");
|
|
@@ -49,13 +52,61 @@ function generateServerEntry(ctx) {
|
|
|
49
52
|
*/
|
|
50
53
|
function writeServerEntry(ctx) {
|
|
51
54
|
mkdirSync(dirname(ctx.serverEntry), { recursive: true });
|
|
52
|
-
|
|
55
|
+
writeFileAtomic(ctx.serverEntry, generateServerEntry(ctx));
|
|
56
|
+
writeDiscoveryManifest(ctx);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Writes a file through a temporary sibling that is renamed into place, so a
|
|
60
|
+
* reader never observes it truncated. The worker can re-import the entry while a
|
|
61
|
+
* rescan is rewriting it, when files are edited in quick succession.
|
|
62
|
+
*
|
|
63
|
+
* @param path - The destination path.
|
|
64
|
+
* @param content - The file content.
|
|
65
|
+
*/
|
|
66
|
+
function writeFileAtomic(path, content) {
|
|
67
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
68
|
+
writeFileSync(tmp, content, "utf8");
|
|
69
|
+
renameSync(tmp, path);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Writes what the scanner found as JSON, next to the generated server entry.
|
|
73
|
+
*
|
|
74
|
+
* The server entry is executable code keyed only by class name: it discards the
|
|
75
|
+
* file each class came from, the routes read out of the source and the HTTP
|
|
76
|
+
* methods. That information exists at build time and is exactly what a runtime
|
|
77
|
+
* inspector cannot recover, so it is persisted here and picked up by core's
|
|
78
|
+
* `discovery` introspection section.
|
|
79
|
+
*
|
|
80
|
+
* @param ctx - The plugin context holding the discovery result.
|
|
81
|
+
*/
|
|
82
|
+
function writeDiscoveryManifest(ctx) {
|
|
83
|
+
const manifest = {
|
|
84
|
+
root: ctx.root,
|
|
85
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
86
|
+
controllers: ctx.controllers.map((entry) => ({
|
|
87
|
+
name: entry.importClassName,
|
|
88
|
+
path: entry.path
|
|
89
|
+
})),
|
|
90
|
+
services: ctx.services.map((entry) => ({
|
|
91
|
+
name: entry.importClassName,
|
|
92
|
+
path: entry.path
|
|
93
|
+
})),
|
|
94
|
+
middlewares: (ctx.middlewares ?? []).map((entry) => ({
|
|
95
|
+
name: entry.importClassName,
|
|
96
|
+
path: entry.path
|
|
97
|
+
})),
|
|
98
|
+
routes: ctx.routes.map((route) => ({
|
|
99
|
+
method: route.method,
|
|
100
|
+
path: route.route,
|
|
101
|
+
controller: route.importClassName,
|
|
102
|
+
file: route.path
|
|
103
|
+
}))
|
|
104
|
+
};
|
|
105
|
+
writeFileAtomic(join(dirname(ctx.serverEntry), "discovery.json"), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
53
106
|
}
|
|
54
107
|
//#endregion
|
|
55
108
|
//#region src/context.ts
|
|
56
109
|
const DEFAULT_SCAN_DIRS = ["src"];
|
|
57
|
-
/** Location of the generated server entry, kept inside node_modules so it stays out of source control and file watchers. */
|
|
58
|
-
const SERVER_ENTRY_REL = "node_modules/.vercube/server-entry.mjs";
|
|
59
110
|
/** Build output directory (relative to root) for the frontend client, served by the built server in production. */
|
|
60
111
|
const CLIENT_OUT_DIR = "dist/public";
|
|
61
112
|
/**
|
|
@@ -71,12 +122,13 @@ function createContext(pluginConfig) {
|
|
|
71
122
|
pluginConfig,
|
|
72
123
|
root,
|
|
73
124
|
scanDirs: [],
|
|
74
|
-
serverEntry:
|
|
125
|
+
serverEntry: serverEntryPath(root),
|
|
75
126
|
dev: true,
|
|
76
127
|
hasClient: false,
|
|
77
128
|
controllers: [],
|
|
78
129
|
routes: [],
|
|
79
|
-
services: []
|
|
130
|
+
services: [],
|
|
131
|
+
middlewares: []
|
|
80
132
|
};
|
|
81
133
|
}
|
|
82
134
|
/**
|
|
@@ -89,7 +141,7 @@ function createContext(pluginConfig) {
|
|
|
89
141
|
async function setupContext(ctx, options) {
|
|
90
142
|
ctx.root = ctx.pluginConfig.rootDir ? resolveFrom(options.root, ctx.pluginConfig.rootDir) : options.root;
|
|
91
143
|
ctx.dev = options.dev;
|
|
92
|
-
ctx.serverEntry =
|
|
144
|
+
ctx.serverEntry = serverEntryPath(ctx.root);
|
|
93
145
|
ctx.hasClient = existsSync(resolve(ctx.root, "index.html"));
|
|
94
146
|
ctx.scanDirs = (ctx.pluginConfig.scanDirs ?? DEFAULT_SCAN_DIRS).map((dir) => resolveFrom(ctx.root, dir));
|
|
95
147
|
ctx.setupFile = ctx.pluginConfig.setupFile ? resolveFrom(ctx.root, ctx.pluginConfig.setupFile) : void 0;
|
|
@@ -102,13 +154,31 @@ async function setupContext(ctx, options) {
|
|
|
102
154
|
* @param ctx - The plugin context to refresh.
|
|
103
155
|
*/
|
|
104
156
|
async function scanProject(ctx) {
|
|
105
|
-
const { controllers, routes, services } = await scanSource({ dirs: ctx.scanDirs });
|
|
157
|
+
const { controllers, routes, services, middlewares } = await scanSource({ dirs: ctx.scanDirs });
|
|
106
158
|
ctx.controllers = controllers;
|
|
107
159
|
ctx.routes = routes;
|
|
108
160
|
ctx.services = services;
|
|
161
|
+
ctx.middlewares = middlewares;
|
|
109
162
|
writeServerEntry(ctx);
|
|
110
163
|
}
|
|
111
164
|
/**
|
|
165
|
+
* Location of the generated server entry, kept inside node_modules so it stays
|
|
166
|
+
* out of source control and file watchers.
|
|
167
|
+
*
|
|
168
|
+
* The file name carries a hash of the project root. The entry imports discovered
|
|
169
|
+
* classes by absolute path, so two dev servers reaching one checkout through
|
|
170
|
+
* different paths (the host and a container with the project mounted at
|
|
171
|
+
* `/var/www`) would otherwise overwrite each other's entry, and whichever reloads
|
|
172
|
+
* next would import paths that do not exist on its side.
|
|
173
|
+
*
|
|
174
|
+
* @param root - The absolute project root.
|
|
175
|
+
* @returns The absolute path of the server entry for that root.
|
|
176
|
+
*/
|
|
177
|
+
function serverEntryPath(root) {
|
|
178
|
+
const hash = createHash("sha256").update(root).digest("hex").slice(0, 8);
|
|
179
|
+
return resolve(root, `node_modules/.vercube/server-entry.${hash}.mjs`);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
112
182
|
* Resolves `target` against `base` when it is relative, leaving absolute paths untouched.
|
|
113
183
|
*/
|
|
114
184
|
function resolveFrom(base, target) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as scanProject } from "./context-
|
|
1
|
+
import { r as scanProject } from "./context-D4q0XCl9.mjs";
|
|
2
2
|
import { t as VERCUBE_ENV } from "./types-Cpd32Lkt.mjs";
|
|
3
3
|
import { createViteHotChannel } from "env-runner/vite";
|
|
4
4
|
import { watch } from "chokidar";
|