@taujs/server 0.0.1 → 0.0.4

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,26 +1,244 @@
1
1
  # @taujs/server
2
2
 
3
- Streaming React SSR & Hydration Fastify Plugin for integration with taujs [ τjs ] template
3
+ `npm install @taujs/server`
4
+
5
+ `yarn add @taujs/server`
6
+
7
+ `pnpm add @taujs/server`
8
+
9
+ ## Streaming React SSR & Hydration
10
+
11
+ Fastify Plugin for integration with taujs [ τjs ] template https://github.com/aoede3/taujs
12
+
13
+ - Production: Fastify, React
14
+ - Development: Fastify, React, tsx, Vite
4
15
 
5
16
  TypeScript / ESM-only focus
6
- τjs - DX: Developer eXperience first focus. Integrated ViteDevServer HMR + Vite Runtime API run alongside tsx (TS eXecute) providing fast responsive dev reload times for both backend / frontend
7
17
 
8
- Build: client (Vite) & server (ESBuild + Rollup)
18
+ ## τjs - Developer eXperience
19
+
20
+ Integrated ViteDevServer HMR + Vite Runtime API run alongside tsx (TS eXecute) providing fast responsive dev reload times for both backend / frontend
9
21
 
10
- Fastify https://fastify.dev/
11
- Vite https://vitejs.dev/guide/ssr#building-for-production
12
- React https://reactjs.org/
22
+ - Fastify https://fastify.dev/
23
+ - React https://reactjs.org/
24
+ - tsx https://tsx.is/
25
+ - Vite https://vitejs.dev/guide/ssr#building-for-production
13
26
 
14
- tsx https://tsx.is/
15
- ViteDevServer HMR https://vitejs.dev/guide/ssr#setting-up-the-dev-server
16
- Vite Runtime API https://vitejs.dev/guide/api-vite-runtime
17
- ESBuild https://esbuild.github.io/
18
- Rollup https://rollupjs.org/
19
- ESM https://nodejs.org/api/esm.html
27
+ - ViteDevServer HMR https://vitejs.dev/guide/ssr#setting-up-the-dev-server
28
+ - Vite Runtime API https://vitejs.dev/guide/api-vite-runtime
29
+ - ESBuild https://esbuild.github.io/
30
+ - Rollup https://rollupjs.org/
31
+ - ESM https://nodejs.org/api/esm.html
20
32
 
21
33
  ## Development / CI
22
34
 
23
- "@fastify/static": "^7.0.4",
24
- "path-to-regexp": "^7.0.0"
35
+ `npm install --legacy-peer-deps`
36
+
37
+ ## Usage
38
+
39
+ ### Fastify
40
+
41
+ ```
42
+ import { SSRServer } from '@taujs/server;
43
+
44
+ void (await fastify.register(SSRServer, {
45
+ clientEntryClient: 'entry-client',
46
+ clientEntryServer: 'entry-server',
47
+ clientHtmlTemplate: 'index.html',
48
+ clientRoot: path.resolve(__dirname, '../client'),
49
+ routes,
50
+ serviceRegistry,
51
+ }));
52
+ ```
53
+
54
+ Not utilising taujs [ τjs ] template? Add in your own `alias` object for your own particular setup e.g. `alias: { object }`
55
+
56
+ ### React 'entry-client.tsx'
57
+
58
+ ```
59
+ import React from 'react';
60
+ import { hydrateRoot } from 'react-dom/client';
61
+ import { createSSRStore, SSRStoreProvider } from '@taujs/server/data-store';
62
+
63
+ import AppBootstrap from './AppBootstrap';
64
+
65
+ const bootstrap = () => {
66
+ const initialDataPromise = Promise.resolve(window.__INITIAL_DATA__);
67
+ const store = createSSRStore(initialDataPromise);
68
+
69
+ hydrateRoot(
70
+ document.getElementById('root') as HTMLElement,
71
+ <SSRStoreProvider store={store}>
72
+ <AppBootstrap />
73
+ </SSRStoreProvider>,
74
+ );
75
+ };
76
+
77
+ if (document.readyState !== 'loading') {
78
+ bootstrap();
79
+ } else {
80
+ document.addEventListener('DOMContentLoaded', () => {
81
+ bootstrap();
82
+ });
83
+ }
84
+
85
+ ```
86
+
87
+ ### React 'entry-server.tsx'
88
+
89
+ Extended pipe object with callbacks to @taujs/server enabling additional manipulation of HEAD content from client code
90
+
91
+ ```
92
+ import { ServerResponse } from 'node:http';
93
+
94
+ import React from 'react';
95
+ import { createSSRStore, SSRStoreProvider } from '@taujs/server/data-store';
96
+ import { createStreamRenderer } from '@taujs/server/render';
97
+
98
+ import AppBootstrap from '@client/AppBootstrap';
99
+
100
+ import type { RenderCallbacks } from '@taujs/server';
101
+
102
+ export const streamRender = (
103
+ serverResponse: ServerResponse,
104
+ { onHead, onFinish, onError }: RenderCallbacks,
105
+ initialDataPromise: Promise<Record<string, unknown>>,
106
+ bootstrapModules: string,
107
+ ) => {
108
+ const store = createSSRStore(initialDataPromise);
109
+
110
+ const headContent = `
111
+ <meta name="description" content="taujs [ τjs ]">
112
+ <link rel="icon" type="image/svg+xml" href="/taujs.svg" />
113
+ <title>taujs [ τjs ]</title>
114
+ `;
115
+
116
+ createStreamRenderer(
117
+ serverResponse,
118
+ { onHead, onFinish, onError },
119
+ {
120
+ appElement: (
121
+ <SSRStoreProvider store={store}>
122
+ <AppBootstrap />
123
+ </SSRStoreProvider>
124
+ ),
125
+ bootstrapModules,
126
+ getStoreSnapshot: store.getSnapshot,
127
+ headContent,
128
+ },
129
+ );
130
+ };
131
+
132
+ ```
133
+
134
+ ### index.html
135
+
136
+ ```
137
+ <!DOCTYPE html>
138
+ <html lang="en">
139
+ <head>
140
+ <meta charset="UTF-8" />
141
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
142
+ <!--ssr-head-->
143
+ </head>
144
+ <body>
145
+ <main id="root"><!--ssr-html--></main>
146
+ </body>
147
+ </html>
148
+ ```
149
+
150
+ ### client.d.ts
151
+
152
+ ```
153
+ interface Window {
154
+ __INITIAL_DATA__: Record<string, unknown>;
155
+ }
156
+ ```
157
+
158
+ ### Routes
159
+
160
+ Integral to τjs is its internal routing:
161
+
162
+ 1. Fastify serving index.html to client browser for client routing
163
+ 2. Internal service calls to API to provide data for streaming/hydration
164
+ 3. Fastify serving API calls via HTTP in the more traditional sense of client/server
165
+
166
+ In ensuring a particular 'route' receives data for hydration there are two options:
167
+
168
+ 1. An HTTP call elsewhere syntactically not unlike 'fetch' providing params to a 'fetch' call
169
+ 2. Internally calling a service which in turn will make 'call' to return data as per your architecture
170
+
171
+ In supporting Option 2. there is a registry of services. More detail in 'Service Registry'.
172
+
173
+ Each routes 'path' is a simple URL regex as per below examples.
174
+
175
+ ```
176
+ import type { Route, RouteParams } from '@taujs/server';
177
+
178
+ export const routes: Route<RouteParams>[] = [
179
+ {
180
+ path: '/',
181
+ attributes: {
182
+ fetch: async () => {
183
+ return {
184
+ url: 'http://localhost:5173/api/initial',
185
+ options: {
186
+ method: 'GET',
187
+ },
188
+ };
189
+ },
190
+ },
191
+ },
192
+ {
193
+ path: '/:id',
194
+ attributes: {
195
+ fetch: async (params: RouteParams) => {
196
+ return {
197
+ url: `http://localhost:5173/api/initial/${params.id}`,
198
+ options: {
199
+ method: 'GET',
200
+ },
201
+ };
202
+ },
203
+ },
204
+ },
205
+ {
206
+ path: '/:id/:another',
207
+ attributes: {
208
+ fetch: async (params: RouteParams) => {
209
+ return {
210
+ options: { params },
211
+ serviceMethod: 'exampleMethod',
212
+ serviceName: 'ServiceExample',
213
+ };
214
+ },
215
+ },
216
+ },
217
+ ];
218
+ ```
219
+
220
+ ### Service Registry
221
+
222
+ In supporting internal calls via τjs a registry of available services and methods provides the linkage to your own architectural setup and developmental patterns
223
+
224
+ ```
225
+ import { ServiceExample } from './ServiceExample';
226
+
227
+ import type { ServiceRegistry } from '@taujs/server';
228
+
229
+ export const serviceRegistry: ServiceRegistry = {
230
+ ServiceExample,
231
+ };
232
+ ```
25
233
 
26
- npm install --legacy-peer-deps
234
+ ```
235
+ export const ServiceExample = {
236
+ async exampleMethod(params: Record<string, unknown>): Promise<Record<string, unknown>> {
237
+ return new Promise((resolve) => {
238
+ setTimeout(() => {
239
+ resolve({ hello: `world internal service call response with id: ${params.id} and another: ${params.another}` });
240
+ }, 5500);
241
+ });
242
+ },
243
+ };
244
+ ```
@@ -12,4 +12,4 @@ declare const SSRStoreProvider: React.FC<React.PropsWithChildren<{
12
12
  }>>;
13
13
  declare const useSSRStore: <T>() => SSRStore<T>;
14
14
 
15
- export { SSRStoreProvider, createSSRStore, useSSRStore };
15
+ export { type SSRStore, SSRStoreProvider, createSSRStore, useSSRStore };
@@ -0,0 +1,18 @@
1
+ import { ServerResponse } from 'node:http';
2
+ import React from 'react';
3
+
4
+ type RenderCallbacks = {
5
+ onHead: (headContent: string) => void;
6
+ onFinish: (initialDataResolved: unknown) => void;
7
+ onError: (error: unknown) => void;
8
+ };
9
+
10
+ type StreamRender = {
11
+ appElement: React.JSX.Element;
12
+ bootstrapModules: string;
13
+ headContent: string;
14
+ getStoreSnapshot: () => unknown;
15
+ };
16
+ declare const createStreamRenderer: (serverResponse: ServerResponse, { onHead, onFinish, onError }: RenderCallbacks, { appElement, bootstrapModules, headContent, getStoreSnapshot }: StreamRender) => void;
17
+
18
+ export { createStreamRenderer };
@@ -0,0 +1,31 @@
1
+ // src/SSRRender.ts
2
+ import { Writable } from "node:stream";
3
+ import "react";
4
+ import { renderToPipeableStream } from "react-dom/server";
5
+ var createStreamRenderer = (serverResponse, { onHead, onFinish, onError }, { appElement, bootstrapModules, headContent, getStoreSnapshot }) => {
6
+ const { pipe } = renderToPipeableStream(appElement, {
7
+ bootstrapModules: [bootstrapModules],
8
+ onShellReady() {
9
+ onHead(headContent);
10
+ pipe(
11
+ new Writable({
12
+ write(chunk, _encoding, callback) {
13
+ serverResponse.write(chunk, callback);
14
+ },
15
+ final(callback) {
16
+ onFinish(getStoreSnapshot());
17
+ callback();
18
+ }
19
+ })
20
+ );
21
+ },
22
+ onAllReady() {
23
+ },
24
+ onError(error) {
25
+ onError(error);
26
+ }
27
+ });
28
+ };
29
+ export {
30
+ createStreamRenderer
31
+ };
@@ -21,6 +21,7 @@ type FetchConfig = {
21
21
  serviceMethod?: string;
22
22
  };
23
23
  type SSRServerOptions = {
24
+ alias: Record<string, string>;
24
25
  clientRoot: string;
25
26
  clientHtmlTemplate: string;
26
27
  clientEntryClient: string;
package/dist/SSRServer.js CHANGED
@@ -244,7 +244,7 @@ var overrideCSSHMRConsoleError = () => {
244
244
  // src/SSRServer.ts
245
245
  var SSRServer = (0, import_fastify_plugin.default)(
246
246
  async (app, opts) => {
247
- const { clientRoot, clientHtmlTemplate, clientEntryClient, clientEntryServer, routes, serviceRegistry, isDebug } = opts;
247
+ const { alias, clientRoot, clientHtmlTemplate, clientEntryClient, clientEntryServer, routes, serviceRegistry, isDebug } = opts;
248
248
  const templateHtmlPath = path.join(clientRoot, clientHtmlTemplate);
249
249
  const templateHtml = !isDevelopment ? await fs.readFile(templateHtmlPath, "utf-8") : await fs.readFile(path.join(clientRoot, clientHtmlTemplate), "utf-8");
250
250
  const ssrManifestPath = path.join(clientRoot, ".vite/ssr-manifest.json");
@@ -283,9 +283,12 @@ var SSRServer = (0, import_fastify_plugin.default)(
283
283
  ],
284
284
  resolve: {
285
285
  alias: {
286
- "@client": path.resolve(clientRoot),
287
- "@server": path.resolve(__dirname),
288
- "@shared": path.resolve(__dirname, "../shared")
286
+ ...{
287
+ "@client": path.resolve(clientRoot),
288
+ "@server": path.resolve(__dirname),
289
+ "@shared": path.resolve(__dirname, "../shared")
290
+ },
291
+ ...alias
289
292
  }
290
293
  },
291
294
  root: clientRoot,
package/package.json CHANGED
@@ -20,7 +20,7 @@
20
20
  "type": "git",
21
21
  "url": "git+https://github.com/aoede3/taujs-server.git"
22
22
  },
23
- "version": "0.0.1",
23
+ "version": "0.0.4",
24
24
  "exports": {
25
25
  ".": {
26
26
  "import": "./dist/SSRServer.js",
@@ -30,6 +30,10 @@
30
30
  "import": "./dist/SSRDataStore.js",
31
31
  "types": "./dist/SSRDataStore.d.ts"
32
32
  },
33
+ "./render": {
34
+ "import": "./dist/SSRRender.js",
35
+ "types": "./dist/SSRRender.d.ts"
36
+ },
33
37
  "./package.json": "./package.json"
34
38
  },
35
39
  "files": [
@@ -41,6 +45,9 @@
41
45
  "*": {
42
46
  "data-store": [
43
47
  "./dist/SSRDataStore.d.ts"
48
+ ],
49
+ "render": [
50
+ "./dist/SSRRender.d.ts"
44
51
  ]
45
52
  }
46
53
  },
@@ -55,8 +62,10 @@
55
62
  "@changesets/cli": "^2.27.7",
56
63
  "@types/node": "^20.14.9",
57
64
  "@types/react": "^18.3.3",
65
+ "@types/react-dom": "^18.3.0",
58
66
  "fastify": "^4.28.0",
59
67
  "prettier": "^3.3.3",
68
+ "react-dom": "^18.3.1",
60
69
  "tsup": "^8.2.4",
61
70
  "vite": "^5.4.2",
62
71
  "vitest": "^2.0.5"