@moonsjs/vite 0.1.18 → 0.2.1
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/dist/{index-1ksjfvnz.js → index-0agjty3z.js} +4 -1
- package/dist/{index-1ksjfvnz.js.map → index-0agjty3z.js.map} +3 -3
- package/dist/index-f8w073cm.js +30 -0
- package/dist/index-f8w073cm.js.map +10 -0
- package/dist/index.js +1 -1
- package/dist/server/handler.d.ts +9 -0
- package/dist/server/handler.d.ts.map +1 -1
- package/dist/server/index.js +67 -19
- package/dist/server/index.js.map +4 -4
- package/dist/server/test-server.d.ts +6 -0
- package/dist/server/test-server.d.ts.map +1 -1
- package/dist/testing-preload.d.ts +2 -0
- package/dist/testing-preload.d.ts.map +1 -0
- package/dist/testing-preload.js +10 -0
- package/dist/testing-preload.js.map +10 -0
- package/dist/testing.d.ts +11 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +13 -0
- package/dist/testing.js.map +9 -0
- package/package.json +15 -5
- package/src/server/dev.ts +5 -0
- package/src/server/handler.ts +61 -14
- package/src/server/test-server.ts +48 -1
- package/src/testing-preload.ts +5 -0
- package/src/testing.ts +34 -0
package/src/server/dev.ts
CHANGED
|
@@ -133,6 +133,11 @@ function shouldHandle(req: IncomingMessage, apiPrefix: string | undefined): bool
|
|
|
133
133
|
return true;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
// Addendum O: health check (semua method) — paritas 405 dengan produksi.
|
|
137
|
+
if (path === "/_moons/health") {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
136
141
|
// Addendum D.1: endpoint `src/api` (semua method) dirutekan ke runtime.
|
|
137
142
|
if (apiPrefix !== undefined && (path === apiPrefix || path.startsWith(`${apiPrefix}/`))) {
|
|
138
143
|
return true;
|
package/src/server/handler.ts
CHANGED
|
@@ -21,7 +21,15 @@ import {
|
|
|
21
21
|
} from "@moonsjs/core";
|
|
22
22
|
import { encodeSsrPayload, reviveError, serializeError } from "@moonsjs/core/serialize";
|
|
23
23
|
import { API_METHODS, resolveApiRoute } from "@moonsjs/core";
|
|
24
|
-
import { renderToMarkup } from "@moonsjs/react/ssr";
|
|
24
|
+
import type { renderToMarkup as RenderToMarkupFn } from "@moonsjs/react/ssr";
|
|
25
|
+
|
|
26
|
+
type RenderMarkup = typeof RenderToMarkupFn;
|
|
27
|
+
|
|
28
|
+
/** Default: bare dynamic import (bundlers keep this resolvable). */
|
|
29
|
+
async function defaultRenderer(): Promise<RenderMarkup> {
|
|
30
|
+
const mod = (await import("@moonsjs/react/ssr")) as { renderToMarkup: RenderMarkup };
|
|
31
|
+
return mod.renderToMarkup;
|
|
32
|
+
}
|
|
25
33
|
import type { ClientRouteEntry, RouteModuleShape } from "@moonsjs/react/internal";
|
|
26
34
|
import { createRpcHandler, runWithContext, runWithRequestCache, runWithResponseCollector, takeResponseMutations } from "@moonsjs/server";
|
|
27
35
|
import { NOT_FOUND_ROUTE_KEY } from "../manifest";
|
|
@@ -59,6 +67,12 @@ export interface RequestHandlerOptions {
|
|
|
59
67
|
apiMiddlewares?: ApiMiddlewareDefinition[];
|
|
60
68
|
/** Peta import() `_middleware.ts` pohon api, key = file path. */
|
|
61
69
|
loadApiMiddleware?: Record<string, () => Promise<unknown>>;
|
|
70
|
+
/**
|
|
71
|
+
* Addendum O: sumber `renderToMarkup`. Test server memakai loader ini untuk
|
|
72
|
+
* memuat renderer dari `node_modules` app (Bun meresolusi paket registry dari
|
|
73
|
+
* cache; peer `react-dom` tidak ditemukan dari sana).
|
|
74
|
+
*/
|
|
75
|
+
renderer?: () => Promise<RenderMarkup>;
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
/**
|
|
@@ -67,7 +81,15 @@ export interface RequestHandlerOptions {
|
|
|
67
81
|
*/
|
|
68
82
|
export function createRequestHandler(
|
|
69
83
|
options: RequestHandlerOptions,
|
|
70
|
-
): (request: Request) => Promise<Response> {
|
|
84
|
+
): (request: Request) => Promise<Response> {
|
|
85
|
+
let renderMarkup: RenderMarkup | null = null;
|
|
86
|
+
const getRenderMarkup = async (): Promise<RenderMarkup> => {
|
|
87
|
+
if (renderMarkup === null) {
|
|
88
|
+
renderMarkup = await (options.renderer ?? defaultRenderer)();
|
|
89
|
+
}
|
|
90
|
+
return renderMarkup;
|
|
91
|
+
};
|
|
92
|
+
const { routes, config, routesDir, loadRoute, shell } = options;
|
|
71
93
|
const rpc = createRpcHandler(
|
|
72
94
|
config.security === undefined ? {} : { security: config.security },
|
|
73
95
|
);
|
|
@@ -98,12 +120,15 @@ export function createRequestHandler(
|
|
|
98
120
|
// Addendum B.1: eksekusi `meta()` page (data = hasil loader page itu sendiri).
|
|
99
121
|
const meta = await runRouteMeta(pageModule, data[pageKey], resolved.route.file);
|
|
100
122
|
|
|
101
|
-
const
|
|
123
|
+
const render = await getRenderMarkup();
|
|
124
|
+
const markup = render(
|
|
102
125
|
entry,
|
|
103
126
|
{ page: pageModule as RouteModuleShape, layouts: modules.slice(0, -1) as RouteModuleShape[] },
|
|
104
127
|
resolved.params,
|
|
105
128
|
data,
|
|
106
129
|
searchParams,
|
|
130
|
+
undefined,
|
|
131
|
+
config.trailingSlash,
|
|
107
132
|
);
|
|
108
133
|
const payload = encodeSsrPayload({
|
|
109
134
|
key: pageKey,
|
|
@@ -116,7 +141,10 @@ export function createRequestHandler(
|
|
|
116
141
|
// A1: pindai payload SSR (dev saja — `payloadScan` tidak diisi production).
|
|
117
142
|
warnPayloadFindings(payload, options.payloadScan, warnedPayloadLabels);
|
|
118
143
|
|
|
119
|
-
return htmlResponse(
|
|
144
|
+
return htmlResponse(
|
|
145
|
+
withTrailingSlashMeta(await shell(markup, payload, request.url, meta), config.trailingSlash),
|
|
146
|
+
200,
|
|
147
|
+
);
|
|
120
148
|
}
|
|
121
149
|
|
|
122
150
|
/** Addendum D.1: dispatch endpoint `src/api` (405/HEAD/OPTIONS + error). */
|
|
@@ -217,25 +245,28 @@ export function createRequestHandler(
|
|
|
217
245
|
const key = routeKeyFromFile(routesDir, notFoundRoute.file);
|
|
218
246
|
const [module] = await loadModules([key], loadRoute);
|
|
219
247
|
const payload = encodeSsrPayload({ key: NOT_FOUND_ROUTE_KEY, params: {}, data: {} });
|
|
220
|
-
const
|
|
248
|
+
const render = await getRenderMarkup();
|
|
249
|
+
const markup = render(
|
|
221
250
|
{ page: key, layouts: [] },
|
|
222
251
|
{ page: module as RouteModuleShape, layouts: [] },
|
|
223
252
|
{},
|
|
224
253
|
{},
|
|
254
|
+
{},
|
|
255
|
+
undefined,
|
|
256
|
+
config.trailingSlash,
|
|
257
|
+
);
|
|
258
|
+
return htmlResponse(
|
|
259
|
+
withTrailingSlashMeta(await shell(markup, payload, request.url), config.trailingSlash),
|
|
260
|
+
404,
|
|
225
261
|
);
|
|
226
|
-
return htmlResponse(await shell(markup, payload, request.url), 404);
|
|
227
262
|
} catch (error) {
|
|
228
263
|
console.error("[moons] failed to render custom _404:", error);
|
|
229
264
|
}
|
|
230
265
|
}
|
|
231
266
|
|
|
232
267
|
const payload = encodeSsrPayload({ key: "", params: {}, data: {} });
|
|
233
|
-
const html = await shell(
|
|
234
|
-
|
|
235
|
-
payload,
|
|
236
|
-
request.url,
|
|
237
|
-
);
|
|
238
|
-
return htmlResponse(html, 404);
|
|
268
|
+
const html = await shell("<h1>404</h1><p>Page not found.</p>", payload, request.url);
|
|
269
|
+
return htmlResponse(withTrailingSlashMeta(html, config.trailingSlash), 404);
|
|
239
270
|
}
|
|
240
271
|
|
|
241
272
|
/**
|
|
@@ -265,15 +296,20 @@ export function createRequestHandler(
|
|
|
265
296
|
data: {},
|
|
266
297
|
...(serialized === undefined ? {} : { error: serialized }),
|
|
267
298
|
});
|
|
268
|
-
const
|
|
299
|
+
const render = await getRenderMarkup();
|
|
300
|
+
const markup = render(
|
|
269
301
|
{ page: key, layouts: [] },
|
|
270
302
|
{ page: module as RouteModuleShape, layouts: [] },
|
|
271
303
|
{},
|
|
272
304
|
{},
|
|
273
305
|
{},
|
|
274
306
|
serialized === undefined ? undefined : reviveError(serialized),
|
|
307
|
+
config.trailingSlash,
|
|
308
|
+
);
|
|
309
|
+
return htmlResponse(
|
|
310
|
+
withTrailingSlashMeta(await shell(markup, payload, request.url), config.trailingSlash),
|
|
311
|
+
500,
|
|
275
312
|
);
|
|
276
|
-
return htmlResponse(await shell(markup, payload, request.url), 500);
|
|
277
313
|
} catch (renderError) {
|
|
278
314
|
console.error("[moons] failed to render custom _error:", renderError);
|
|
279
315
|
}
|
|
@@ -617,6 +653,17 @@ function internalErrorResponse(error: unknown, options: { log?: boolean } = {}):
|
|
|
617
653
|
);
|
|
618
654
|
}
|
|
619
655
|
|
|
656
|
+
/** Addendum O: expose the trailing-slash policy to the client runtime. */
|
|
657
|
+
function withTrailingSlashMeta(html: string, policy?: "never" | "always"): string {
|
|
658
|
+
if (policy === undefined) {
|
|
659
|
+
return html;
|
|
660
|
+
}
|
|
661
|
+
return html.replace(
|
|
662
|
+
"</head>",
|
|
663
|
+
` <meta name="moons:trailing-slash" content="${policy}" />\n</head>`,
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
|
|
620
667
|
function htmlResponse(html: string, status: number): Response {
|
|
621
668
|
return new Response(html, {
|
|
622
669
|
status,
|
|
@@ -1,12 +1,51 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
1
4
|
import type { MoonsConfig, RouteDefinition, RouteModule } from "@moonsjs/core";
|
|
2
|
-
import {
|
|
5
|
+
import type { renderToMarkup as RenderToMarkupFn } from "@moonsjs/react/ssr";
|
|
6
|
+
import { createRequestHandler, type RequestHandlerOptions } from "./handler";
|
|
3
7
|
import { DEFAULT_TEMPLATE, injectShell, type Shell } from "./shell";
|
|
4
8
|
|
|
9
|
+
type RenderMarkup = typeof RenderToMarkupFn;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Addendum O: resolve the SSR renderer from the app's own `node_modules` first.
|
|
13
|
+
* Bun resolves registry packages through its install cache; peer imports
|
|
14
|
+
* (`react-dom/server`) are not reachable from there, but they are from the app.
|
|
15
|
+
*/
|
|
16
|
+
async function loadRenderer(candidates: ReadonlyArray<string>): Promise<RenderMarkup> {
|
|
17
|
+
for (const root of candidates) {
|
|
18
|
+
for (const relative of [
|
|
19
|
+
"node_modules/@moonsjs/react/dist/ssr.js",
|
|
20
|
+
"node_modules/@moonsjs/react/dist/ssr.mjs",
|
|
21
|
+
"node_modules/@moonsjs/react/src/ssr.ts",
|
|
22
|
+
]) {
|
|
23
|
+
const file = join(root, relative);
|
|
24
|
+
if (!existsSync(file)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const mod = (await import(pathToFileURL(file).href)) as {
|
|
28
|
+
renderToMarkup?: RenderMarkup;
|
|
29
|
+
};
|
|
30
|
+
if (typeof mod.renderToMarkup === "function") {
|
|
31
|
+
return mod.renderToMarkup;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const fallback = (await import("@moonsjs/react/ssr")) as { renderToMarkup: RenderMarkup };
|
|
36
|
+
return fallback.renderToMarkup;
|
|
37
|
+
}
|
|
38
|
+
|
|
5
39
|
export interface TestServerOptions {
|
|
6
40
|
routes: RouteDefinition[];
|
|
7
41
|
config: MoonsConfig;
|
|
8
42
|
routesDir: string;
|
|
9
43
|
loadRoute: Record<string, () => Promise<RouteModule>>;
|
|
44
|
+
/** Endpoint `src/api` (Addendum O) — hasil scan app. */
|
|
45
|
+
apiRoutes?: RequestHandlerOptions["apiRoutes"];
|
|
46
|
+
loadApi?: RequestHandlerOptions["loadApi"];
|
|
47
|
+
apiMiddlewares?: RequestHandlerOptions["apiMiddlewares"];
|
|
48
|
+
loadApiMiddleware?: RequestHandlerOptions["loadApiMiddleware"];
|
|
10
49
|
/** Default: shell in-memory dari `DEFAULT_TEMPLATE` (tanpa file). */
|
|
11
50
|
shell?: Shell;
|
|
12
51
|
}
|
|
@@ -34,6 +73,14 @@ export function createTestServer(
|
|
|
34
73
|
config: options.config,
|
|
35
74
|
routesDir: options.routesDir,
|
|
36
75
|
loadRoute: options.loadRoute,
|
|
76
|
+
...(options.apiRoutes === undefined ? {} : { apiRoutes: options.apiRoutes }),
|
|
77
|
+
...(options.loadApi === undefined ? {} : { loadApi: options.loadApi }),
|
|
78
|
+
...(options.apiMiddlewares === undefined ? {} : { apiMiddlewares: options.apiMiddlewares }),
|
|
79
|
+
...(options.loadApiMiddleware === undefined
|
|
80
|
+
? {}
|
|
81
|
+
: { loadApiMiddleware: options.loadApiMiddleware }),
|
|
82
|
+
renderer: () =>
|
|
83
|
+
loadRenderer([process.cwd(), resolve(options.routesDir, "..", "..")]),
|
|
37
84
|
shell,
|
|
38
85
|
});
|
|
39
86
|
|
package/src/testing.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import type { BunPlugin } from "bun";
|
|
3
|
+
import { analyzeServerFile, injectServerFnMeta } from "./transform";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Annotate a `*.server.ts` module with the transform-generated serverFn meta
|
|
7
|
+
* (`__hash`, `__file`, `__line`, `__exportName`) — the same injection the
|
|
8
|
+
* build performs. Used by `moons test` so tests can import server modules.
|
|
9
|
+
*/
|
|
10
|
+
export function transformServerModuleForTest(
|
|
11
|
+
file: string,
|
|
12
|
+
projectRoot = process.cwd(),
|
|
13
|
+
): string {
|
|
14
|
+
const source = readFileSync(file, "utf8");
|
|
15
|
+
const analysis = analyzeServerFile(source, file, projectRoot);
|
|
16
|
+
return injectServerFnMeta(source, analysis, projectRoot);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Bun runtime plugin that applies the serverFn meta before tests load code. */
|
|
20
|
+
export function moonsTestPlugin(projectRoot = process.cwd()): BunPlugin {
|
|
21
|
+
return {
|
|
22
|
+
name: "moons-test-server-meta",
|
|
23
|
+
setup(build) {
|
|
24
|
+
build.onLoad({ filter: /\.server\.(ts|tsx)$/ }, (args) => ({
|
|
25
|
+
contents: transformServerModuleForTest(args.path, projectRoot),
|
|
26
|
+
loader: args.path.endsWith(".tsx") ? "tsx" : "ts",
|
|
27
|
+
}));
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function registerMoonsTestPlugin(projectRoot = process.cwd()): void {
|
|
33
|
+
Bun.plugin(moonsTestPlugin(projectRoot));
|
|
34
|
+
}
|