@anchorlib/vite-ssr 1.0.0-beta.24
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/_virtual/rolldown_runtime.js +30 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +186 -0
- package/dist/utils.d.ts +27 -0
- package/dist/utils.js +54 -0
- package/package.json +70 -0
- package/readme.md +57 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
//#region rolldown:runtime
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __commonJS = (cb, mod) => function() {
|
|
11
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
15
|
+
key = keys[i];
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
17
|
+
get: ((k) => from[k]).bind(null, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
export { __commonJS, __require, __toESM };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
type ViteSSROptions = {
|
|
5
|
+
/** Path to the router module. Must `export default` a `Router` instance. */
|
|
6
|
+
router: string;
|
|
7
|
+
/** Path to the root layout module. Must `export default` a `RouteComponent`. */
|
|
8
|
+
layout: string;
|
|
9
|
+
/**
|
|
10
|
+
* Path to the renderer module (e.g., `'@anchorlib/react/ssr'`).
|
|
11
|
+
* Must export `createSSR(router, layout) => SSRRenderer`.
|
|
12
|
+
*/
|
|
13
|
+
renderer: string;
|
|
14
|
+
/**
|
|
15
|
+
* IRPC configuration. If provided, POST requests to the transport
|
|
16
|
+
* endpoint are routed through the HTTPRouter.
|
|
17
|
+
*/
|
|
18
|
+
irpc?: {
|
|
19
|
+
/** IRPC instance. String loads `export default`, object loads a named export. */
|
|
20
|
+
module: ModuleRef;
|
|
21
|
+
/** HTTP Transport instance. */
|
|
22
|
+
transport: ModuleRef;
|
|
23
|
+
/** WebSocket Transport instance (optional). */
|
|
24
|
+
wsTransport?: ModuleRef;
|
|
25
|
+
/** Handler modules to load (e.g., ['./src/pages/constructor.ts']). */
|
|
26
|
+
handlers?: string[];
|
|
27
|
+
};
|
|
28
|
+
/** Placeholder for rendered head. Defaults to `<!--ssr-head-->`. */
|
|
29
|
+
headTag?: string;
|
|
30
|
+
/** Placeholder for rendered body. Defaults to `<!--ssr-outlet-->`. */
|
|
31
|
+
bodyTag?: string;
|
|
32
|
+
};
|
|
33
|
+
/** A module reference — string for default export, object for named export. */
|
|
34
|
+
type ModuleRef = string | {
|
|
35
|
+
path: string;
|
|
36
|
+
name: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Vite plugin for Anchor SSR.
|
|
40
|
+
*
|
|
41
|
+
* Replaces manual `server.ts` and `entry-server.tsx` with a single plugin call.
|
|
42
|
+
* Handles SSR renderer construction, IRPC routing, request isolation,
|
|
43
|
+
* abort signal propagation, and template transformation.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* // vite.config.ts
|
|
48
|
+
* import { airSSR } from '@anchorlib/vite-ssr';
|
|
49
|
+
*
|
|
50
|
+
* export default defineConfig({
|
|
51
|
+
* plugins: [
|
|
52
|
+
* airSSR({
|
|
53
|
+
* router: './src/lib/router.ts',
|
|
54
|
+
* layout: './src/pages/layout.tsx',
|
|
55
|
+
* renderer: '@anchorlib/react/ssr',
|
|
56
|
+
* irpc: {
|
|
57
|
+
* module: './src/lib/irpc.ts',
|
|
58
|
+
* transport: './src/lib/transport.ts',
|
|
59
|
+
* handlers: ['./src/pages/constructor.ts'],
|
|
60
|
+
* },
|
|
61
|
+
* }),
|
|
62
|
+
* ],
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare function airSSR(options: ViteSSROptions): Plugin;
|
|
67
|
+
//#endregion
|
|
68
|
+
export { ViteSSROptions, airSSR };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { sendWebResponse, toWebRequest } from "./utils.js";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { decodeCookies, getContext, setCookieContext } from "@anchorlib/core";
|
|
5
|
+
|
|
6
|
+
//#region src/index.ts
|
|
7
|
+
/**
|
|
8
|
+
* Vite plugin for Anchor SSR.
|
|
9
|
+
*
|
|
10
|
+
* Replaces manual `server.ts` and `entry-server.tsx` with a single plugin call.
|
|
11
|
+
* Handles SSR renderer construction, IRPC routing, request isolation,
|
|
12
|
+
* abort signal propagation, and template transformation.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* // vite.config.ts
|
|
17
|
+
* import { airSSR } from '@anchorlib/vite-ssr';
|
|
18
|
+
*
|
|
19
|
+
* export default defineConfig({
|
|
20
|
+
* plugins: [
|
|
21
|
+
* airSSR({
|
|
22
|
+
* router: './src/lib/router.ts',
|
|
23
|
+
* layout: './src/pages/layout.tsx',
|
|
24
|
+
* renderer: '@anchorlib/react/ssr',
|
|
25
|
+
* irpc: {
|
|
26
|
+
* module: './src/lib/irpc.ts',
|
|
27
|
+
* transport: './src/lib/transport.ts',
|
|
28
|
+
* handlers: ['./src/pages/constructor.ts'],
|
|
29
|
+
* },
|
|
30
|
+
* }),
|
|
31
|
+
* ],
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
function airSSR(options) {
|
|
36
|
+
const { router: routerPath, layout: layoutPath, renderer: rendererPath, irpc: irpcConfig, headTag = "<!--ssr-head-->", bodyTag = "<!--ssr-outlet-->" } = options;
|
|
37
|
+
let router;
|
|
38
|
+
let templatePath;
|
|
39
|
+
let rendererFactory;
|
|
40
|
+
return {
|
|
41
|
+
name: "air-ssr",
|
|
42
|
+
configureServer(server) {
|
|
43
|
+
templatePath = path.resolve(server.config.root, "index.html");
|
|
44
|
+
const ready = (async () => {
|
|
45
|
+
await server.ssrLoadModule("@irpclib/irpc/server").catch(() => {});
|
|
46
|
+
rendererFactory = (await server.ssrLoadModule(rendererPath)).createSSR;
|
|
47
|
+
if (irpcConfig) {
|
|
48
|
+
router = await initRouter(server, irpcConfig);
|
|
49
|
+
if (irpcConfig.wsTransport) await initWsRouter(server, irpcConfig);
|
|
50
|
+
const { IRPC_STORE } = await server.ssrLoadModule("@irpclib/irpc");
|
|
51
|
+
IRPC_STORE.subscribe(() => {
|
|
52
|
+
IRPC_STORE.print();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
})();
|
|
56
|
+
return () => {
|
|
57
|
+
server.middlewares.use(async (req, res, next) => {
|
|
58
|
+
await ready;
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
req.on("close", () => controller.abort());
|
|
61
|
+
try {
|
|
62
|
+
const url = req.originalUrl ?? req.url ?? "/";
|
|
63
|
+
if (router && req.method === "POST" && url.startsWith(router.transport.endpoint)) {
|
|
64
|
+
if (irpcConfig?.handlers) for (const handler of irpcConfig.handlers) await server.ssrLoadModule(handler);
|
|
65
|
+
const request = toWebRequest(req, controller);
|
|
66
|
+
const cookie$1 = req.headers.cookie ?? "";
|
|
67
|
+
await sendWebResponse(res, await router.resolve(request, [["cookie", cookie$1]]));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (url.includes(".")) return next();
|
|
71
|
+
let template = fs.readFileSync(templatePath, "utf-8");
|
|
72
|
+
template = await server.transformIndexHtml(url, template);
|
|
73
|
+
const { default: pageRouter } = await server.ssrLoadModule(routerPath);
|
|
74
|
+
const { default: RootLayout } = await server.ssrLoadModule(layoutPath);
|
|
75
|
+
const render = await rendererFactory(pageRouter, RootLayout);
|
|
76
|
+
const cookie = req.headers.cookie ?? "";
|
|
77
|
+
let ssrResult;
|
|
78
|
+
if (router) {
|
|
79
|
+
const cookieJar = decodeCookies(cookie);
|
|
80
|
+
ssrResult = await router.isolate(() => render(url, cookie, void 0, controller, true), controller, [["cookie", cookie]], () => {
|
|
81
|
+
setCookieContext(cookieJar);
|
|
82
|
+
});
|
|
83
|
+
} else ssrResult = await render(url, cookie, void 0, controller);
|
|
84
|
+
const { html, head, status, redirect, cookies } = ssrResult;
|
|
85
|
+
if (redirect) {
|
|
86
|
+
res.writeHead(302, { Location: redirect });
|
|
87
|
+
res.end();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const headers = { "Content-Type": "text/html" };
|
|
91
|
+
if (cookies?.length) headers["Set-Cookie"] = cookies;
|
|
92
|
+
const page = template.replace(headTag, head).replace(bodyTag, html);
|
|
93
|
+
res.writeHead(status ?? 200, headers);
|
|
94
|
+
res.end(page);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
next(error);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Loads an export from a module reference.
|
|
105
|
+
* String path loads the default export; { path, name } loads a named export.
|
|
106
|
+
*/
|
|
107
|
+
async function loadExport(server, ref) {
|
|
108
|
+
if (typeof ref === "string") return (await server.ssrLoadModule(ref)).default;
|
|
109
|
+
return (await server.ssrLoadModule(ref.path))[ref.name];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Initializes the IRPC HTTP router by loading the module and handlers.
|
|
113
|
+
*/
|
|
114
|
+
async function initRouter(server, config) {
|
|
115
|
+
try {
|
|
116
|
+
const irpc = await loadExport(server, config.module);
|
|
117
|
+
const transport = await loadExport(server, config.transport);
|
|
118
|
+
if (!irpc || !transport) {
|
|
119
|
+
server.config.logger.warn("[air-ssr] IRPC module and transport are required.");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (config.handlers) for (const handler of config.handlers) await server.ssrLoadModule(handler);
|
|
123
|
+
const { HTTPRouter } = await server.ssrLoadModule("@irpclib/http/router");
|
|
124
|
+
const router = new HTTPRouter(irpc, transport);
|
|
125
|
+
router.use(() => {
|
|
126
|
+
setCookieContext(decodeCookies(getContext("cookie", "")));
|
|
127
|
+
});
|
|
128
|
+
server.config.logger.info(`[air-ssr] IRPC HTTP router initialized at ${transport.endpoint}`);
|
|
129
|
+
return router;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
server.config.logger.error("[air-ssr] Failed to initialize IRPC HTTP router:");
|
|
132
|
+
server.config.logger.error(String(error));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Initializes the IRPC WebSocket router and attaches it to the Vite dev server.
|
|
138
|
+
*
|
|
139
|
+
* Creates a WebSocketServer on the same HTTP server (noServer mode),
|
|
140
|
+
* intercepts upgrade requests at the WS transport endpoint, and routes
|
|
141
|
+
* messages through WebSocketRouter.
|
|
142
|
+
*/
|
|
143
|
+
async function initWsRouter(server, config) {
|
|
144
|
+
if (!config.wsTransport) return;
|
|
145
|
+
try {
|
|
146
|
+
const irpc = await loadExport(server, config.module);
|
|
147
|
+
const wsTransport = await loadExport(server, config.wsTransport);
|
|
148
|
+
if (!irpc || !wsTransport) {
|
|
149
|
+
server.config.logger.warn("[air-ssr] IRPC module and wsTransport are required for WebSocket.");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const { WebSocketRouter } = await server.ssrLoadModule("@irpclib/ws/router");
|
|
153
|
+
const wsRouter = new WebSocketRouter(irpc, wsTransport);
|
|
154
|
+
wsRouter.use(() => {
|
|
155
|
+
setCookieContext(decodeCookies(getContext("cookie", "")));
|
|
156
|
+
});
|
|
157
|
+
const endpoint = wsTransport.endpoint;
|
|
158
|
+
const { WebSocketServer } = await import("./node_modules/.bun/ws@8.21.0/node_modules/ws/wrapper.js");
|
|
159
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
160
|
+
server.httpServer?.on("upgrade", (req, socket, head) => {
|
|
161
|
+
if (req.url === endpoint) wss.handleUpgrade(req, socket, head, (ws) => {
|
|
162
|
+
wss.emit("connection", ws, req);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
wss.on("connection", (ws, req) => {
|
|
166
|
+
const cookie = req.headers.cookie ?? "";
|
|
167
|
+
ws.on("message", async (data) => {
|
|
168
|
+
if (config.handlers) for (const handler of config.handlers) await server.ssrLoadModule(handler);
|
|
169
|
+
const message = data instanceof ArrayBuffer ? data : data.toString();
|
|
170
|
+
await wsRouter.resolve(message, ws, [["cookie", cookie]]);
|
|
171
|
+
});
|
|
172
|
+
ws.on("close", () => {
|
|
173
|
+
wsRouter.disconnect();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
server.config.logger.info(`[air-ssr] IRPC WebSocket router initialized at ${endpoint}`);
|
|
177
|
+
return wsRouter;
|
|
178
|
+
} catch (error) {
|
|
179
|
+
server.config.logger.error("[air-ssr] Failed to initialize IRPC WebSocket router:");
|
|
180
|
+
server.config.logger.error(String(error));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
//#endregion
|
|
186
|
+
export { airSSR };
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
|
|
3
|
+
//#region src/utils.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Converts a Node.js IncomingMessage into a Web Standard Request.
|
|
7
|
+
*
|
|
8
|
+
* Wires the AbortController so that `req.on('close')` (client disconnect)
|
|
9
|
+
* propagates as `request.signal.aborted`, enabling IRPC stream cleanup.
|
|
10
|
+
*
|
|
11
|
+
* @param req - The incoming Node.js HTTP request.
|
|
12
|
+
* @param controller - AbortController to wire to the request signal.
|
|
13
|
+
* @returns A Web Standard Request.
|
|
14
|
+
*/
|
|
15
|
+
declare function toWebRequest(req: IncomingMessage, controller: AbortController): Request;
|
|
16
|
+
/**
|
|
17
|
+
* Pipes a Web Standard Response back into a Node.js ServerResponse.
|
|
18
|
+
*
|
|
19
|
+
* Handles both streaming and non-streaming responses. For streaming responses,
|
|
20
|
+
* pipes the ReadableStream with proper backpressure handling.
|
|
21
|
+
*
|
|
22
|
+
* @param res - The Node.js ServerResponse to write to.
|
|
23
|
+
* @param response - The Web Standard Response to send.
|
|
24
|
+
*/
|
|
25
|
+
declare function sendWebResponse(res: ServerResponse, response: Response): Promise<void>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { sendWebResponse, toWebRequest };
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
|
|
3
|
+
//#region src/utils.ts
|
|
4
|
+
/**
|
|
5
|
+
* Converts a Node.js IncomingMessage into a Web Standard Request.
|
|
6
|
+
*
|
|
7
|
+
* Wires the AbortController so that `req.on('close')` (client disconnect)
|
|
8
|
+
* propagates as `request.signal.aborted`, enabling IRPC stream cleanup.
|
|
9
|
+
*
|
|
10
|
+
* @param req - The incoming Node.js HTTP request.
|
|
11
|
+
* @param controller - AbortController to wire to the request signal.
|
|
12
|
+
* @returns A Web Standard Request.
|
|
13
|
+
*/
|
|
14
|
+
function toWebRequest(req, controller) {
|
|
15
|
+
const { method = "GET", headers, url = "/" } = req;
|
|
16
|
+
const origin = `http://${headers.host ?? "localhost"}`;
|
|
17
|
+
const fullUrl = new URL(url, origin).href;
|
|
18
|
+
const isBodyMethod = method !== "GET" && method !== "HEAD";
|
|
19
|
+
return new Request(fullUrl, {
|
|
20
|
+
method,
|
|
21
|
+
headers,
|
|
22
|
+
body: isBodyMethod ? Readable.toWeb(req) : void 0,
|
|
23
|
+
signal: controller.signal,
|
|
24
|
+
duplex: "half"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Pipes a Web Standard Response back into a Node.js ServerResponse.
|
|
29
|
+
*
|
|
30
|
+
* Handles both streaming and non-streaming responses. For streaming responses,
|
|
31
|
+
* pipes the ReadableStream with proper backpressure handling.
|
|
32
|
+
*
|
|
33
|
+
* @param res - The Node.js ServerResponse to write to.
|
|
34
|
+
* @param response - The Web Standard Response to send.
|
|
35
|
+
*/
|
|
36
|
+
async function sendWebResponse(res, response) {
|
|
37
|
+
response.headers.forEach((value, key) => {
|
|
38
|
+
if (key.toLowerCase() !== "content-encoding") res.setHeader(key, value);
|
|
39
|
+
});
|
|
40
|
+
res.writeHead(response.status, response.statusText);
|
|
41
|
+
if (response.body) {
|
|
42
|
+
const readable = Readable.fromWeb(response.body);
|
|
43
|
+
readable.pipe(res);
|
|
44
|
+
res.on("close", () => {
|
|
45
|
+
if (!readable.destroyed) readable.destroy();
|
|
46
|
+
});
|
|
47
|
+
} else {
|
|
48
|
+
const text = await response.text();
|
|
49
|
+
res.end(text);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { sendWebResponse, toWebRequest };
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"name": "@anchorlib/vite-ssr",
|
|
4
|
+
"version": "1.0.0-beta.24",
|
|
5
|
+
"description": "Vite plugin that handles SSR rendering and IRPC routing for AIR Stack applications",
|
|
6
|
+
"keywords": ["vite", "ssr", "anchor", "irpc"],
|
|
7
|
+
"author": "Nanang Mahdaen El Agung <mahdaen@gmail.com>",
|
|
8
|
+
"homepage": "https://github.com/beerush-id/anchor",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/beerush-id/anchor.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/beerush-id/anchor/issues"
|
|
16
|
+
},
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"module": "./dist/index.js",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": ["dist"],
|
|
26
|
+
"directories": {
|
|
27
|
+
"dist": "./dist"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@anchorlib/core": "^1.0.0-beta.24",
|
|
34
|
+
"@irpclib/http": "^1.0.0-beta.24",
|
|
35
|
+
"@irpclib/irpc": "^1.0.0-beta.24",
|
|
36
|
+
"@irpclib/ws": "^1.0.0-beta.24",
|
|
37
|
+
"typescript": "^5.9.3",
|
|
38
|
+
"vite": ">=5"
|
|
39
|
+
},
|
|
40
|
+
"peerDependenciesMeta": {
|
|
41
|
+
"@anchorlib/core": {
|
|
42
|
+
"optional": true
|
|
43
|
+
},
|
|
44
|
+
"@irpclib/irpc": {
|
|
45
|
+
"optional": true
|
|
46
|
+
},
|
|
47
|
+
"@irpclib/http": {
|
|
48
|
+
"optional": true
|
|
49
|
+
},
|
|
50
|
+
"@irpclib/ws": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@biomejs/biome": "2.3.3",
|
|
56
|
+
"@types/ws": "latest",
|
|
57
|
+
"publint": "0.3.15",
|
|
58
|
+
"rimraf": "6.0.1",
|
|
59
|
+
"tsdown": "0.15.9",
|
|
60
|
+
"vite": "8.0.10",
|
|
61
|
+
"ws": "latest"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"dev": "rimraf dist && tsdown --watch ./src",
|
|
65
|
+
"clean": "rimraf dist",
|
|
66
|
+
"build": "rimraf dist && tsdown && publint",
|
|
67
|
+
"prepublish": "rimraf dist && tsdown && publint",
|
|
68
|
+
"format": "biome format --write"
|
|
69
|
+
}
|
|
70
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# @anchorlib/vite-ssr
|
|
2
|
+
|
|
3
|
+
Vite plugin that handles SSR rendering and IRPC routing for AIR Stack applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **SSR + IRPC** — Server-side rendering and RPC handling in one plugin
|
|
8
|
+
- **Zero Boilerplate** — No manual server entry or renderer wiring needed
|
|
9
|
+
- **Request Isolation** — Cookies, abort signals, and hooks scoped per request
|
|
10
|
+
- **Hot Module Replacement** — Edit handlers and components without restarting the server
|
|
11
|
+
- **Framework Agnostic** — Works with any renderer that exports `createSSR`
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @anchorlib/vite-ssr
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// vite.config.ts
|
|
23
|
+
import { airSSR } from '@anchorlib/vite-ssr';
|
|
24
|
+
|
|
25
|
+
export default defineConfig({
|
|
26
|
+
plugins: [
|
|
27
|
+
react(),
|
|
28
|
+
airSSR({
|
|
29
|
+
router: './src/lib/router.ts',
|
|
30
|
+
layout: './src/pages/layout.tsx',
|
|
31
|
+
renderer: '@anchorlib/react/ssr',
|
|
32
|
+
irpc: {
|
|
33
|
+
module: './src/lib/module.ts',
|
|
34
|
+
handlers: ['./src/pages/constructor.ts'],
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Options
|
|
42
|
+
|
|
43
|
+
| Option | Required | Description |
|
|
44
|
+
|--------|----------|-------------|
|
|
45
|
+
| `router` | Yes | Path to the router module. Must `export default` a `Router` instance. |
|
|
46
|
+
| `layout` | Yes | Path to the root layout module. Must `export default` a `RouteComponent`. |
|
|
47
|
+
| `renderer` | Yes | Path to the renderer module (e.g., `'@anchorlib/react/ssr'`). Must export `createSSR`. |
|
|
48
|
+
| `irpc.module` | No | IRPC instance. String for default export, `{ path, name }` for named export. |
|
|
49
|
+
| `irpc.transport` | No | HTTP Transport instance. Same format as `module`. |
|
|
50
|
+
| `irpc.wsTransport` | No | WebSocket Transport instance. Same format as `module`. |
|
|
51
|
+
| `irpc.handlers` | No | Handler modules to load (e.g., `['./src/pages/constructor.ts']`). |
|
|
52
|
+
| `headTag` | No | Placeholder for rendered head. Defaults to `<!--ssr-head-->`. |
|
|
53
|
+
| `bodyTag` | No | Placeholder for rendered body. Defaults to `<!--ssr-outlet-->`. |
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
MIT
|