@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 CHANGED
@@ -1,18 +1,27 @@
1
- # Vite SSR
1
+ # Vite SSR Plugin
2
2
 
3
- SSR for Vite. And optional API servers. Build with Vite's new [Environment API](https://vite.dev/guide/api-environment.html).
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
- ## Getting Started
5
+ ## Features
6
6
 
7
- Install the SSR Vite plugin.
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
- Configure the plugin in your Vite config by providing the client entry, the SSR entry, and optionally one or more API entries.
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. The keys are also used as base path for the API requests. Eg. `/api*` requests will be sent to the `api` API.
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
- Setup your client entry.
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
- Setup your SSR entry. This serves the HTML based on the request.
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
- // src/entry-ssr.ts
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
- Optionally, setup your API entry.
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
- ## Production
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
- First update your package.json to build all environments by adding the `--app` flag to the `vite build` script.
78
- Also add a `serve` script to run the server.
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
- Setup 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.
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 for production.
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
- const transformedHtml = await server.transformIndexHtml("/", `<html><head></head><body></body></html>`);
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
- const apiFetch = await apiModules[api]();
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.9",
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.js",
16
- "types": "./dist/plugin.d.ts"
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.2.5",
46
- "@types/node": "^24.1.0",
47
- "tsdown": "^0.15.6",
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.1.7"
49
+ "vite": "^7.2.2"
50
50
  },
51
51
  "dependencies": {
52
- "@hono/node-server": "^1.18.0",
52
+ "@hono/node-server": "^1.19.6",
53
53
  "cheerio": "^1.1.2"
54
54
  }
55
55
  }
File without changes