@alepha/react 0.7.0 → 0.7.2
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 +1 -1
- package/dist/index.browser.cjs +59 -22
- package/dist/index.browser.js +42 -6
- package/dist/index.cjs +152 -85
- package/dist/index.d.ts +317 -206
- package/dist/index.js +130 -64
- package/dist/{useActive-DjpZBEuB.cjs → useRouterState-C2uo0jXu.cjs} +319 -140
- package/dist/{useActive-BX41CqY8.js → useRouterState-D5__ZcUV.js} +321 -143
- package/package.json +11 -10
- package/src/components/ClientOnly.tsx +35 -0
- package/src/components/ErrorBoundary.tsx +1 -1
- package/src/components/ErrorViewer.tsx +161 -0
- package/src/components/Link.tsx +9 -3
- package/src/components/NestedView.tsx +18 -3
- package/src/components/NotFound.tsx +30 -0
- package/src/descriptors/$page.ts +141 -30
- package/src/errors/RedirectionError.ts +4 -1
- package/src/hooks/RouterHookApi.ts +42 -24
- package/src/hooks/useAlepha.ts +12 -0
- package/src/hooks/useClient.ts +8 -6
- package/src/hooks/useInject.ts +2 -2
- package/src/hooks/useQueryParams.ts +1 -1
- package/src/hooks/useRouter.ts +6 -0
- package/src/index.browser.ts +4 -2
- package/src/index.shared.ts +11 -5
- package/src/index.ts +3 -4
- package/src/providers/BrowserRouterProvider.ts +4 -3
- package/src/providers/PageDescriptorProvider.ts +91 -20
- package/src/providers/ReactBrowserProvider.ts +6 -59
- package/src/providers/ReactBrowserRenderer.ts +72 -0
- package/src/providers/ReactServerProvider.ts +200 -81
- package/dist/index.browser.cjs.map +0 -1
- package/dist/index.browser.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/useActive-BX41CqY8.js.map +0 -1
- package/dist/useActive-DjpZBEuB.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { t, $logger, $inject, Alepha, $hook, OPTIONS, __bind } from '@alepha/core';
|
|
2
|
-
import { ServerRouterProvider, ServerLinksProvider, ServerModule } from '@alepha/server';
|
|
3
|
-
import { P as PageDescriptorProvider, $ as $page } from './
|
|
4
|
-
export { E as ErrorBoundary, L as Link, N as NestedView, l as ReactBrowserProvider,
|
|
2
|
+
import { ServerRouterProvider, ServerTimingProvider, ServerLinksProvider, apiLinksResponseSchema, ServerModule } from '@alepha/server';
|
|
3
|
+
import { P as PageDescriptorProvider, $ as $page } from './useRouterState-D5__ZcUV.js';
|
|
4
|
+
export { C as ClientOnly, E as ErrorBoundary, L as Link, N as NestedView, l as ReactBrowserProvider, R as RedirectionError, a as RouterContext, c as RouterHookApi, b as RouterLayerContext, k as isPageRoute, u as useActive, d as useAlepha, e as useClient, f as useInject, g as useQueryParams, h as useRouter, i as useRouterEvents, j as useRouterState } from './useRouterState-D5__ZcUV.js';
|
|
5
5
|
import { existsSync } from 'node:fs';
|
|
6
|
-
import { readFile } from 'node:fs/promises';
|
|
7
6
|
import { join } from 'node:path';
|
|
8
7
|
import { ServerStaticProvider } from '@alepha/server-static';
|
|
9
8
|
import { renderToString } from 'react-dom/server';
|
|
10
9
|
import 'react/jsx-runtime';
|
|
11
10
|
import 'react';
|
|
12
|
-
import 'react-dom/client';
|
|
13
11
|
import '@alepha/router';
|
|
14
12
|
|
|
15
13
|
class ServerHeadProvider {
|
|
@@ -75,9 +73,9 @@ class ServerHeadProvider {
|
|
|
75
73
|
}
|
|
76
74
|
|
|
77
75
|
const envSchema = t.object({
|
|
78
|
-
REACT_SERVER_DIST: t.string({ default: "
|
|
76
|
+
REACT_SERVER_DIST: t.string({ default: "public" }),
|
|
79
77
|
REACT_SERVER_PREFIX: t.string({ default: "" }),
|
|
80
|
-
REACT_SSR_ENABLED: t.boolean(
|
|
78
|
+
REACT_SSR_ENABLED: t.optional(t.boolean()),
|
|
81
79
|
REACT_ROOT_ID: t.string({ default: "root" })
|
|
82
80
|
});
|
|
83
81
|
class ReactServerProvider {
|
|
@@ -87,46 +85,75 @@ class ReactServerProvider {
|
|
|
87
85
|
serverStaticProvider = $inject(ServerStaticProvider);
|
|
88
86
|
serverRouterProvider = $inject(ServerRouterProvider);
|
|
89
87
|
headProvider = $inject(ServerHeadProvider);
|
|
88
|
+
serverTimingProvider = $inject(ServerTimingProvider);
|
|
90
89
|
env = $inject(envSchema);
|
|
91
90
|
ROOT_DIV_REGEX = new RegExp(
|
|
92
91
|
`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`,
|
|
93
92
|
"is"
|
|
94
93
|
);
|
|
95
|
-
|
|
94
|
+
onConfigure = $hook({
|
|
96
95
|
name: "configure",
|
|
97
96
|
handler: async () => {
|
|
98
97
|
const pages = this.alepha.getDescriptorValues($page);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
98
|
+
const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
|
|
99
|
+
this.alepha.state("ReactServerProvider.ssr", ssrEnabled);
|
|
102
100
|
for (const { key, instance, value } of pages) {
|
|
103
101
|
const name = value[OPTIONS].name ?? key;
|
|
102
|
+
instance[key].prerender = this.createRenderFunction(name, true);
|
|
104
103
|
if (this.alepha.isTest()) {
|
|
105
104
|
instance[key].render = this.createRenderFunction(name);
|
|
106
105
|
}
|
|
107
106
|
}
|
|
108
107
|
if (this.alepha.isServerless() === "vite") {
|
|
109
|
-
await this.configureVite();
|
|
108
|
+
await this.configureVite(ssrEnabled);
|
|
110
109
|
return;
|
|
111
110
|
}
|
|
112
111
|
let root = "";
|
|
113
112
|
if (!this.alepha.isServerless()) {
|
|
114
113
|
root = this.getPublicDirectory();
|
|
115
114
|
if (!root) {
|
|
116
|
-
this.log.warn(
|
|
117
|
-
|
|
115
|
+
this.log.warn(
|
|
116
|
+
"Missing static files, static file server will be disabled"
|
|
117
|
+
);
|
|
118
|
+
} else {
|
|
119
|
+
this.log.debug(`Using static files from: ${root}`);
|
|
120
|
+
await this.configureStaticServer(root);
|
|
118
121
|
}
|
|
119
|
-
await this.configureStaticServer(root);
|
|
120
122
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
if (ssrEnabled) {
|
|
124
|
+
await this.registerPages(async () => this.template);
|
|
125
|
+
this.log.info("SSR OK");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.log.info("SSR is disabled, use History API fallback");
|
|
129
|
+
await this.serverRouterProvider.route({
|
|
130
|
+
path: "*",
|
|
131
|
+
handler: async ({ url, reply }) => {
|
|
132
|
+
if (url.pathname.includes(".")) {
|
|
133
|
+
reply.headers["content-type"] = "text/plain";
|
|
134
|
+
reply.body = "Not Found";
|
|
135
|
+
reply.status = 404;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
reply.headers["content-type"] = "text/html";
|
|
139
|
+
return this.template;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
124
142
|
}
|
|
125
143
|
});
|
|
144
|
+
get template() {
|
|
145
|
+
return this.alepha.state("ReactServerProvider.template");
|
|
146
|
+
}
|
|
126
147
|
async registerPages(templateLoader) {
|
|
127
148
|
for (const page of this.pageDescriptorProvider.getPages()) {
|
|
149
|
+
if (page.children?.length) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
128
152
|
this.log.debug(`+ ${page.match} -> ${page.name}`);
|
|
129
153
|
await this.serverRouterProvider.route({
|
|
154
|
+
...page,
|
|
155
|
+
schema: void 0,
|
|
156
|
+
// schema is handled by the page descriptor provider for now (shared by browser and server)
|
|
130
157
|
method: "GET",
|
|
131
158
|
path: page.match,
|
|
132
159
|
handler: this.createHandler(page, templateLoader)
|
|
@@ -135,8 +162,8 @@ class ReactServerProvider {
|
|
|
135
162
|
}
|
|
136
163
|
getPublicDirectory() {
|
|
137
164
|
const maybe = [
|
|
138
|
-
join(process.cwd(), this.env.REACT_SERVER_DIST),
|
|
139
|
-
join(process.cwd(),
|
|
165
|
+
join(process.cwd(), `dist/${this.env.REACT_SERVER_DIST}`),
|
|
166
|
+
join(process.cwd(), this.env.REACT_SERVER_DIST)
|
|
140
167
|
];
|
|
141
168
|
for (const it of maybe) {
|
|
142
169
|
if (existsSync(it)) {
|
|
@@ -151,23 +178,25 @@ class ReactServerProvider {
|
|
|
151
178
|
path: this.env.REACT_SERVER_PREFIX
|
|
152
179
|
});
|
|
153
180
|
}
|
|
154
|
-
async configureVite() {
|
|
155
|
-
|
|
181
|
+
async configureVite(ssrEnabled) {
|
|
182
|
+
if (!ssrEnabled) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
156
185
|
this.log.info("SSR (vite) OK");
|
|
157
|
-
|
|
158
|
-
const templateUrl = `${url}/index.html`;
|
|
186
|
+
const url = `http://${process.env.SERVER_HOST}:${process.env.SERVER_PORT}`;
|
|
159
187
|
await this.registerPages(
|
|
160
|
-
() => fetch(
|
|
188
|
+
() => fetch(`${url}/index.html`).then((it) => it.text()).catch(() => void 0)
|
|
161
189
|
);
|
|
162
190
|
}
|
|
163
191
|
/**
|
|
164
192
|
* For testing purposes, creates a render function that can be used.
|
|
165
193
|
*/
|
|
166
|
-
createRenderFunction(name) {
|
|
194
|
+
createRenderFunction(name, withIndex = false) {
|
|
167
195
|
return async (options = {}) => {
|
|
168
196
|
const page = this.pageDescriptorProvider.page(name);
|
|
197
|
+
const url = new URL(this.pageDescriptorProvider.url(name, options));
|
|
169
198
|
const context = {
|
|
170
|
-
url
|
|
199
|
+
url,
|
|
171
200
|
params: options.params ?? {},
|
|
172
201
|
query: options.query ?? {},
|
|
173
202
|
head: {},
|
|
@@ -177,7 +206,18 @@ class ReactServerProvider {
|
|
|
177
206
|
page,
|
|
178
207
|
context
|
|
179
208
|
);
|
|
180
|
-
|
|
209
|
+
if (!withIndex) {
|
|
210
|
+
return {
|
|
211
|
+
context,
|
|
212
|
+
html: renderToString(
|
|
213
|
+
this.pageDescriptorProvider.root(state, context)
|
|
214
|
+
)
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
context,
|
|
219
|
+
html: this.renderToHtml(this.template ?? "", state, context)
|
|
220
|
+
};
|
|
181
221
|
};
|
|
182
222
|
}
|
|
183
223
|
createHandler(page, templateLoader) {
|
|
@@ -197,11 +237,24 @@ class ReactServerProvider {
|
|
|
197
237
|
};
|
|
198
238
|
if (this.alepha.has(ServerLinksProvider)) {
|
|
199
239
|
const srv = this.alepha.get(ServerLinksProvider);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
240
|
+
const schema = apiLinksResponseSchema;
|
|
241
|
+
context.links = this.alepha.parse(
|
|
242
|
+
schema,
|
|
243
|
+
await srv.getLinks({
|
|
244
|
+
user: serverRequest.user,
|
|
245
|
+
authorization: serverRequest.headers.authorization
|
|
246
|
+
})
|
|
247
|
+
);
|
|
248
|
+
this.alepha.context.set("links", context.links);
|
|
249
|
+
}
|
|
250
|
+
let target = page;
|
|
251
|
+
while (target) {
|
|
252
|
+
if (page.can && !page.can()) {
|
|
253
|
+
reply.status = 403;
|
|
254
|
+
reply.headers["content-type"] = "text/plain";
|
|
255
|
+
return "Forbidden";
|
|
256
|
+
}
|
|
257
|
+
target = target.parent;
|
|
205
258
|
}
|
|
206
259
|
await this.alepha.emit(
|
|
207
260
|
"react:server:render",
|
|
@@ -213,49 +266,63 @@ class ReactServerProvider {
|
|
|
213
266
|
log: false
|
|
214
267
|
}
|
|
215
268
|
);
|
|
269
|
+
this.serverTimingProvider.beginTiming("createLayers");
|
|
216
270
|
const state = await this.pageDescriptorProvider.createLayers(
|
|
217
271
|
page,
|
|
218
272
|
context
|
|
219
273
|
);
|
|
274
|
+
this.serverTimingProvider.endTiming("createLayers");
|
|
220
275
|
if (state.redirect) {
|
|
221
276
|
return reply.redirect(state.redirect);
|
|
222
277
|
}
|
|
223
|
-
const element = this.pageDescriptorProvider.root(state, context);
|
|
224
|
-
const app = renderToString(element);
|
|
225
|
-
const hydrationData = {
|
|
226
|
-
links: context.links,
|
|
227
|
-
layers: state.layers.map((it) => ({
|
|
228
|
-
...it,
|
|
229
|
-
error: it.error ? {
|
|
230
|
-
...it.error,
|
|
231
|
-
name: it.error.name,
|
|
232
|
-
message: it.error.message,
|
|
233
|
-
stack: it.error.stack
|
|
234
|
-
// TODO: Hide stack in production ?
|
|
235
|
-
} : void 0,
|
|
236
|
-
index: void 0,
|
|
237
|
-
path: void 0,
|
|
238
|
-
element: void 0
|
|
239
|
-
}))
|
|
240
|
-
};
|
|
241
|
-
const script = `<script>window.__ssr=${JSON.stringify(hydrationData)}<\/script>`;
|
|
242
|
-
const response = {
|
|
243
|
-
html: template
|
|
244
|
-
};
|
|
245
|
-
reply.status = 200;
|
|
246
278
|
reply.headers["content-type"] = "text/html";
|
|
247
279
|
reply.headers["cache-control"] = "no-store, no-cache, must-revalidate, proxy-revalidate";
|
|
248
280
|
reply.headers.pragma = "no-cache";
|
|
249
281
|
reply.headers.expires = "0";
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
response.html = this.headProvider.renderHead(
|
|
253
|
-
response.html,
|
|
254
|
-
context.head
|
|
255
|
-
);
|
|
282
|
+
if (page.cache && serverRequest.user) {
|
|
283
|
+
delete context.links;
|
|
256
284
|
}
|
|
257
|
-
|
|
285
|
+
const html = this.renderToHtml(template, state, context);
|
|
286
|
+
page.afterHandler?.(serverRequest);
|
|
287
|
+
return html;
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
renderToHtml(template, state, context) {
|
|
291
|
+
const element = this.pageDescriptorProvider.root(state, context);
|
|
292
|
+
this.serverTimingProvider.beginTiming("renderToString");
|
|
293
|
+
let app = "";
|
|
294
|
+
try {
|
|
295
|
+
app = renderToString(element);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
this.log.error("Error during SSR", error);
|
|
298
|
+
app = renderToString(context.onError(error));
|
|
299
|
+
}
|
|
300
|
+
this.serverTimingProvider.endTiming("renderToString");
|
|
301
|
+
const hydrationData = {
|
|
302
|
+
links: context.links,
|
|
303
|
+
layers: state.layers.map((it) => ({
|
|
304
|
+
...it,
|
|
305
|
+
error: it.error ? {
|
|
306
|
+
...it.error,
|
|
307
|
+
name: it.error.name,
|
|
308
|
+
message: it.error.message,
|
|
309
|
+
stack: it.error.stack
|
|
310
|
+
// TODO: Hide stack in production ?
|
|
311
|
+
} : void 0,
|
|
312
|
+
index: void 0,
|
|
313
|
+
path: void 0,
|
|
314
|
+
element: void 0
|
|
315
|
+
}))
|
|
316
|
+
};
|
|
317
|
+
const script = `<script>window.__ssr=${JSON.stringify(hydrationData)}<\/script>`;
|
|
318
|
+
const response = {
|
|
319
|
+
html: template
|
|
258
320
|
};
|
|
321
|
+
this.fillTemplate(response, app, script);
|
|
322
|
+
if (context.head) {
|
|
323
|
+
response.html = this.headProvider.renderHead(response.html, context.head);
|
|
324
|
+
}
|
|
325
|
+
return response.html;
|
|
259
326
|
}
|
|
260
327
|
fillTemplate(response, app, script) {
|
|
261
328
|
if (this.ROOT_DIV_REGEX.test(response.html)) {
|
|
@@ -294,4 +361,3 @@ class ReactModule {
|
|
|
294
361
|
__bind($page, ReactModule);
|
|
295
362
|
|
|
296
363
|
export { $page, PageDescriptorProvider, ReactModule, ReactServerProvider, envSchema };
|
|
297
|
-
//# sourceMappingURL=index.js.map
|