@havelaer/vite-plugin-ssr 0.0.9 → 0.0.11
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 +177 -27
- package/dist/{plugin.js → plugin.mjs} +4 -8
- package/package.json +8 -8
- /package/dist/{plugin.d.ts → plugin.d.mts} +0 -0
package/README.md
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
|
-
# Vite SSR
|
|
1
|
+
# Vite SSR Plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A powerful Vite plugin that enables Server-Side Rendering (SSR) with optional API servers. Built with Vite's new [Environment API](https://vite.dev/guide/api-environment.html) for optimal performance and developer experience.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- 🚀 **Built with Vite's Environment API** - Leverages the latest Vite features for optimal performance
|
|
8
|
+
- ⚡ **Hot Module Replacement** - Full HMR support for both client and server code
|
|
9
|
+
- 🔧 **Flexible API Configuration** - Support for multiple API endpoints with custom routing
|
|
10
|
+
- 🎯 **TypeScript Support** - Full TypeScript support with comprehensive type definitions
|
|
11
|
+
- 📦 **Zero Configuration** - Works out of the box with sensible defaults
|
|
12
|
+
- 🔄 **Direct API Calls** - Call APIs directly from SSR context without HTTP roundtrips
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
8
15
|
|
|
9
16
|
```bash
|
|
10
17
|
npm install @havelaer/vite-plugin-ssr
|
|
11
18
|
```
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
Configure the plugin in your Vite config by providing the client entry, SSR entry, and optionally one or more API entries.
|
|
14
23
|
|
|
15
|
-
The keys in the `apis` object are the names of the APIs.
|
|
24
|
+
The keys in the `apis` object are the names of the APIs. These keys are also used as base paths for API requests. For example, `/api/*` requests will be sent to the `api` API.
|
|
16
25
|
|
|
17
26
|
```ts
|
|
18
27
|
// vite.config.ts
|
|
@@ -30,7 +39,11 @@ export default defineConfig({
|
|
|
30
39
|
});
|
|
31
40
|
```
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
## Client Entry
|
|
43
|
+
|
|
44
|
+
Set up your client entry point. This is where your client-side JavaScript will be initialized.
|
|
45
|
+
|
|
46
|
+
Example client entry with a fetch call to the API:
|
|
34
47
|
|
|
35
48
|
```ts
|
|
36
49
|
// src/entry-client.ts
|
|
@@ -41,23 +54,17 @@ fetch("/api").then((res) => res.json()).then((data) => {
|
|
|
41
54
|
});
|
|
42
55
|
```
|
|
43
56
|
|
|
44
|
-
|
|
57
|
+
## API Entry
|
|
58
|
+
|
|
59
|
+
Optionally, set up your API entry points. Each API entry should export a default function that handles HTTP requests.
|
|
60
|
+
|
|
61
|
+
Always use the following function signature. We're using the [Fetch API Web standards](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API):
|
|
45
62
|
|
|
46
63
|
```ts
|
|
47
|
-
|
|
48
|
-
export default async (request: Request, ctx: Context): Promise<Response> => {
|
|
49
|
-
return new Response(`
|
|
50
|
-
<p>Hello from server</p>
|
|
51
|
-
<script src="${ctx.assets.js[0].path}" type="module"></script>
|
|
52
|
-
`, {
|
|
53
|
-
headers: {
|
|
54
|
-
"Content-Type": "text/html",
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
}
|
|
64
|
+
export default (request: Request) => Promise<Response>;
|
|
58
65
|
```
|
|
59
66
|
|
|
60
|
-
|
|
67
|
+
Example API entry:
|
|
61
68
|
|
|
62
69
|
```ts
|
|
63
70
|
// src/entry-api.ts
|
|
@@ -69,13 +76,83 @@ export default async (request: Request): Promise<Response> => {
|
|
|
69
76
|
"Content-Type": "application/json",
|
|
70
77
|
},
|
|
71
78
|
});
|
|
72
|
-
}
|
|
79
|
+
};
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## SSR Entry
|
|
83
|
+
|
|
84
|
+
Set up your SSR entry point. This serves the HTML based on the request and handles server-side rendering.
|
|
85
|
+
|
|
86
|
+
The function signature is the same as the API entry, but with an additional context object:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
type HtmlAssets = {
|
|
90
|
+
js: string;
|
|
91
|
+
css: string[];
|
|
92
|
+
};
|
|
93
|
+
type Context<TApis extends string = never> = {
|
|
94
|
+
assets: HtmlAssets;
|
|
95
|
+
apis: { [P in TApis]: APIHandler };
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export default (request: Request, ctx: Context) => Promise<Response>;
|
|
73
99
|
```
|
|
74
100
|
|
|
75
|
-
|
|
101
|
+
The context object contains the assets and the API handlers. This means you can call APIs from the SSR context directly without HTTP roundtrips:
|
|
76
102
|
|
|
77
|
-
|
|
78
|
-
|
|
103
|
+
Example calling the API from the SSR context:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
// src/entry-ssr.ts
|
|
107
|
+
import type { Context } from "@havelaer/vite-plugin-ssr";
|
|
108
|
+
|
|
109
|
+
export default async (request: Request, ctx: Context<"api">): Promise<Response> => {
|
|
110
|
+
// Create an API Request
|
|
111
|
+
const apiRequest = new Request(/* ... */);
|
|
112
|
+
|
|
113
|
+
// Call the API Handler from the SSR Handler directly without doing a HTTP roundtrip
|
|
114
|
+
const apiResponse = await ctx.apis.api(apiRequest).then(r => r.json());
|
|
115
|
+
|
|
116
|
+
return new Response(/* rendered html */, {
|
|
117
|
+
headers: {
|
|
118
|
+
"Content-Type": "text/html",
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The context object also contains the client entry and CSS assets. In development mode, `ctx.assets.js` points to the client entry source (e.g., "src/entry-client.ts") processed and served by Vite.
|
|
125
|
+
|
|
126
|
+
In production, `ctx.assets.js` points to the client entry bundle (e.g., "dist/client/entry-client-[hash].js").
|
|
127
|
+
|
|
128
|
+
Simple SSR entry example:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
// src/entry-ssr.ts
|
|
132
|
+
export default async (request: Request, ctx: Context): Promise<Response> => {
|
|
133
|
+
return new Response(`
|
|
134
|
+
<html>
|
|
135
|
+
<head>
|
|
136
|
+
${ctx.assets.css.map(css => `<link rel="stylesheet" href="${css}" />`).join('\n')}
|
|
137
|
+
</head>
|
|
138
|
+
<body>
|
|
139
|
+
<p>Hello from server</p>
|
|
140
|
+
<script src="${ctx.assets.js}" type="module"></script>
|
|
141
|
+
</body>
|
|
142
|
+
</html>
|
|
143
|
+
`, {
|
|
144
|
+
headers: {
|
|
145
|
+
"Content-Type": "text/html",
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Production Setup
|
|
152
|
+
|
|
153
|
+
### 1. Update package.json
|
|
154
|
+
|
|
155
|
+
First, update your `package.json` to build all environments by adding the `--app` flag to the `vite build` script. Also add a `serve` script to run the server.
|
|
79
156
|
|
|
80
157
|
```json
|
|
81
158
|
{
|
|
@@ -87,7 +164,9 @@ Also add a `serve` script to run the server.
|
|
|
87
164
|
}
|
|
88
165
|
```
|
|
89
166
|
|
|
90
|
-
|
|
167
|
+
### 2. Create a Production Server
|
|
168
|
+
|
|
169
|
+
Set up a server. You can use any server and any runtime. For this example, we're using [Hono](https://hono.dev) with the Node.js runtime.
|
|
91
170
|
|
|
92
171
|
```js
|
|
93
172
|
// server.js
|
|
@@ -98,10 +177,13 @@ import { api, ssr, ssrContext } from "./dist/index.js";
|
|
|
98
177
|
|
|
99
178
|
const app = new Hono();
|
|
100
179
|
|
|
180
|
+
// Serve static assets from the client build
|
|
101
181
|
app.use(serveStatic({ root: "./dist/client" }));
|
|
102
182
|
|
|
183
|
+
// Handle API routes
|
|
103
184
|
app.use("/api/*", (c) => api(c.req.raw));
|
|
104
185
|
|
|
186
|
+
// Handle SSR for all other routes
|
|
105
187
|
app.use((c) => ssr(c.req.raw, ssrContext));
|
|
106
188
|
|
|
107
189
|
serve(app, (info) => {
|
|
@@ -109,18 +191,86 @@ serve(app, (info) => {
|
|
|
109
191
|
});
|
|
110
192
|
```
|
|
111
193
|
|
|
112
|
-
Build
|
|
194
|
+
### 3. Build and Run
|
|
195
|
+
|
|
196
|
+
Build for production:
|
|
113
197
|
|
|
114
198
|
```bash
|
|
115
199
|
npm run build
|
|
116
200
|
```
|
|
117
201
|
|
|
118
|
-
Run the server
|
|
202
|
+
Run the server:
|
|
119
203
|
|
|
120
204
|
```bash
|
|
121
205
|
npm run serve
|
|
122
206
|
```
|
|
123
207
|
|
|
208
|
+
## Advanced Configuration
|
|
209
|
+
|
|
210
|
+
### Custom API Routes
|
|
211
|
+
|
|
212
|
+
You can customize API routes by providing a configuration object instead of just a string:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
// vite.config.ts
|
|
216
|
+
export default defineConfig({
|
|
217
|
+
plugins: [ssr({
|
|
218
|
+
client: "src/entry-client.ts",
|
|
219
|
+
ssr: "src/entry-ssr.ts",
|
|
220
|
+
apis: {
|
|
221
|
+
api: {
|
|
222
|
+
entry: "src/entry-api.ts",
|
|
223
|
+
route: "/custom-api" // Custom route instead of /api
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
})],
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Environment Customization
|
|
231
|
+
|
|
232
|
+
You can customize the build environment for each entry:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
// vite.config.ts
|
|
236
|
+
export default defineConfig({
|
|
237
|
+
plugins: [ssr({
|
|
238
|
+
client: {
|
|
239
|
+
entry: "src/entry-client.ts",
|
|
240
|
+
environment: (env) => ({
|
|
241
|
+
...env,
|
|
242
|
+
build: {
|
|
243
|
+
...env.build,
|
|
244
|
+
rollupOptions: {
|
|
245
|
+
...env.build?.rollupOptions,
|
|
246
|
+
external: ["some-external-dependency"]
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
},
|
|
251
|
+
ssr: "src/entry-ssr.ts",
|
|
252
|
+
apis: {
|
|
253
|
+
api: "src/entry-api.ts",
|
|
254
|
+
},
|
|
255
|
+
})],
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Examples
|
|
260
|
+
|
|
261
|
+
This repository includes working examples in the `examples/` directory:
|
|
262
|
+
|
|
263
|
+
- **Basic Example** (`examples/basic/`) - A minimal setup with client, SSR, and API entries
|
|
264
|
+
- **React Example** (`examples/react/`) - A React application with SSR support
|
|
265
|
+
|
|
266
|
+
To run an example:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
cd examples/basic
|
|
270
|
+
npm install
|
|
271
|
+
npm run dev
|
|
272
|
+
```
|
|
273
|
+
|
|
124
274
|
## License
|
|
125
275
|
|
|
126
276
|
MIT
|
|
@@ -10,11 +10,9 @@ function resolveOptions(options) {
|
|
|
10
10
|
client: typeof options.client === "string" ? { entry: options.client } : options.client,
|
|
11
11
|
ssr: typeof options.ssr === "string" ? { entry: options.ssr } : options.ssr,
|
|
12
12
|
apis: options.apis ? Object.entries(options.apis).reduce((apis, [api, config]) => {
|
|
13
|
-
const entry = typeof config === "string" ? config : config.entry;
|
|
14
|
-
const route = typeof config !== "string" && config.route ? config.route : `/${api}`;
|
|
15
13
|
apis[api] = {
|
|
16
|
-
entry,
|
|
17
|
-
route
|
|
14
|
+
entry: typeof config === "string" ? config : config.entry,
|
|
15
|
+
route: typeof config !== "string" && config.route ? config.route : `/${api}`
|
|
18
16
|
};
|
|
19
17
|
return apis;
|
|
20
18
|
}, {}) : {}
|
|
@@ -113,16 +111,14 @@ function ssrPlugin(options) {
|
|
|
113
111
|
async configureServer(server) {
|
|
114
112
|
viteServer = server;
|
|
115
113
|
const ssrRunner = createServerModuleRunner(server.environments.ssr);
|
|
116
|
-
|
|
117
|
-
injectedScripts = extractHtmlScripts(transformedHtml);
|
|
114
|
+
injectedScripts = extractHtmlScripts(await server.transformIndexHtml("/", `<html><head></head><body></body></html>`));
|
|
118
115
|
const apiModules = {};
|
|
119
116
|
apiEntries.forEach(([api, config]) => {
|
|
120
117
|
const moduleRunner = createServerModuleRunner(server.environments[api]);
|
|
121
118
|
apiModules[api] = () => moduleRunner.import(config.entry).then((m) => m.default);
|
|
122
119
|
server.middlewares.use(async (req, res, next) => {
|
|
123
120
|
if (req.url?.startsWith(config.route)) {
|
|
124
|
-
|
|
125
|
-
await getRequestListener(apiFetch)(req, res);
|
|
121
|
+
await getRequestListener(await apiModules[api]())(req, res);
|
|
126
122
|
return;
|
|
127
123
|
}
|
|
128
124
|
next();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@havelaer/vite-plugin-ssr",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "SSR plugin for Vite. With optional API servers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
15
|
-
"import": "./dist/plugin.
|
|
16
|
-
"types": "./dist/plugin.d.
|
|
15
|
+
"import": "./dist/plugin.mjs",
|
|
16
|
+
"types": "./dist/plugin.d.mts"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"examples/*"
|
|
43
43
|
],
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@biomejs/biome": "2.
|
|
46
|
-
"@types/node": "^24.
|
|
47
|
-
"tsdown": "^0.
|
|
45
|
+
"@biomejs/biome": "2.3.4",
|
|
46
|
+
"@types/node": "^24.10.0",
|
|
47
|
+
"tsdown": "^0.16.1",
|
|
48
48
|
"typescript": "^5.9.3",
|
|
49
|
-
"vite": "^7.
|
|
49
|
+
"vite": "^7.2.2"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@hono/node-server": "^1.
|
|
52
|
+
"@hono/node-server": "^1.19.6",
|
|
53
53
|
"cheerio": "^1.1.2"
|
|
54
54
|
}
|
|
55
55
|
}
|
|
File without changes
|