@lwrjs/lwc-ssr 0.17.2-alpha.2 → 0.17.2-alpha.20
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/build/cjs/__mocks__/renderer.cjs +6 -6
- package/build/cjs/dataViewTransformer/index.cjs +17 -12
- package/build/cjs/fetchController.cjs +31 -31
- package/build/cjs/moduleLoader.cjs +2 -3
- package/build/cjs/renderer.cjs +35 -52
- package/build/cjs/serverBootstrapServices.cjs +3 -3
- package/build/cjs/utils.cjs +9 -65
- package/build/cjs/viewProvider/index.cjs +23 -39
- package/build/cjs/viewTransformer/index.cjs +31 -48
- package/build/es/dataViewTransformer/index.js +22 -15
- package/build/es/fetchController.d.ts +0 -2
- package/build/es/fetchController.js +36 -33
- package/build/es/moduleLoader.js +2 -3
- package/build/es/renderer.d.ts +2 -2
- package/build/es/renderer.js +42 -58
- package/build/es/serverBootstrapServices.d.ts +1 -1
- package/build/es/serverBootstrapServices.js +4 -4
- package/build/es/utils.d.ts +2 -15
- package/build/es/utils.js +7 -76
- package/build/es/viewProvider/index.js +27 -45
- package/build/es/viewTransformer/index.js +38 -64
- package/package.json +12 -12
|
@@ -48,12 +48,8 @@ function lwcSsrViewTransformer(options, {config, moduleBundler, resourceRegistry
|
|
|
48
48
|
if (!metadata.serverData) {
|
|
49
49
|
metadata.serverData = {};
|
|
50
50
|
}
|
|
51
|
-
if (!metadata.serverDebug) {
|
|
52
|
-
metadata.serverDebug = {};
|
|
53
|
-
}
|
|
54
51
|
const allBundles = new Set([...metadata.serverBundles ?? []]);
|
|
55
|
-
const {customElements, serverData
|
|
56
|
-
const {debug} = viewContext.runtimeEnvironment;
|
|
52
|
+
const {customElements, serverData} = metadata;
|
|
57
53
|
const ssrModules = getComponentsToSSR(customElements, stringBuilder);
|
|
58
54
|
const rootSpecifiers = Object.values(ssrModules).map((m) => m.specifier);
|
|
59
55
|
if (!rootSpecifiers.length) {
|
|
@@ -62,33 +58,39 @@ function lwcSsrViewTransformer(options, {config, moduleBundler, resourceRegistry
|
|
|
62
58
|
const islands = rootSpecifiers.join(",");
|
|
63
59
|
let pageTtl;
|
|
64
60
|
await (0, import_instrumentation.getTracer)().trace({name: import_instrumentation.ViewSpan.RenderIsland, attributes: {specifiers: islands}}, async () => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
61
|
+
try {
|
|
62
|
+
const route = routes.find((r) => r.id === viewContext.view.id);
|
|
63
|
+
if (!route) {
|
|
64
|
+
throw new Error(`Unable to resolve configuration for view: ${viewContext.view.id}`);
|
|
65
|
+
}
|
|
66
|
+
const {
|
|
67
|
+
results,
|
|
68
|
+
warnings,
|
|
69
|
+
bundles: islandBundles
|
|
70
|
+
} = await (0, import_renderer.getRenderer)(config, moduleBundler, resourceRegistry).render(ssrModules, route, viewContext.runtimeEnvironment, viewContext.runtimeParams, metadata.serverData, false);
|
|
71
|
+
for (const root in results) {
|
|
72
|
+
const {html, props, cache} = results[root] || {};
|
|
73
|
+
const {tagName, startOffset, endOffset, hydrate} = ssrModules[root];
|
|
74
|
+
pageTtl = (0, import_shared_utils.shortestTtl)(cache?.ttl, pageTtl);
|
|
75
|
+
if (html) {
|
|
76
|
+
let propsAttr = "";
|
|
77
|
+
if (hydrate) {
|
|
78
|
+
const propsId = (0, import_utils.getPropsId)();
|
|
79
|
+
propsAttr = ` ${import_utils.SSR_PROPS_ATTR}="${propsId}"`;
|
|
80
|
+
serverData[propsId] = props;
|
|
81
|
+
}
|
|
82
|
+
const [, remain] = html.split(`<${tagName}`);
|
|
83
|
+
stringBuilder.overwrite(startOffset, endOffset, [`<${tagName}`, propsAttr, remain].join(""));
|
|
84
84
|
}
|
|
85
|
-
const [, remain] = html.split(`<${tagName}`);
|
|
86
|
-
stringBuilder.overwrite(startOffset, endOffset, [`<${tagName}`, propsAttr, remain].join(""));
|
|
87
85
|
}
|
|
86
|
+
(0, import_utils.mergeWarnings)(metadata, warnings);
|
|
87
|
+
metadata.serverBundles = islandBundles ? new Set([...allBundles, ...islandBundles]) : allBundles;
|
|
88
|
+
results && (0, import_shared_utils.addHeadMarkup)(Object.values(results).map((r) => r.markup), stringBuilder);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
if (e instanceof import_diagnostics.LwrError)
|
|
91
|
+
throw e;
|
|
92
|
+
throw new import_diagnostics.LwrApplicationError(import_diagnostics.descriptions.APPLICATION.SSR_ERROR(islands, (0, import_diagnostics.stringifyError)(e)));
|
|
88
93
|
}
|
|
89
|
-
metadata.serverBundles = islandBundles ? new Set([...allBundles, ...islandBundles]) : allBundles;
|
|
90
|
-
results && (0, import_utils.addHeadMarkup)(Object.values(results), stringBuilder);
|
|
91
|
-
errors && handleErrors(errors, customElements, islands, debug, serverDebug);
|
|
92
94
|
});
|
|
93
95
|
import_diagnostics.logger.verbose({
|
|
94
96
|
label: "lwcSsrViewTransformer",
|
|
@@ -118,22 +120,3 @@ function getComponentsToSSR(customElements, stringBuilder) {
|
|
|
118
120
|
}
|
|
119
121
|
return cmpInfo;
|
|
120
122
|
}
|
|
121
|
-
function handleErrors(errors, customElements, specifiers, debug, serverDebug) {
|
|
122
|
-
const allErrors = Object.values(errors).join(", ");
|
|
123
|
-
if (!debug || (0, import_shared_utils.isLocalDev)() && process.env.SSR_THROW_ERRORS === "true") {
|
|
124
|
-
throw new import_diagnostics.LwrApplicationError(import_diagnostics.descriptions.APPLICATION.SSR_ERROR(specifiers, allErrors));
|
|
125
|
-
}
|
|
126
|
-
Object.entries(errors).forEach(([specifier, err]) => {
|
|
127
|
-
const ce = customElements.find(({tagName}) => specifier === (0, import_shared_utils.kebabCaseToModuleSpecifier)(tagName));
|
|
128
|
-
if (ce) {
|
|
129
|
-
ce.props === void 0 ? ce.props = {
|
|
130
|
-
[import_shared_utils.HYDRATE_DIRECTIVE]: import_shared_utils.HYDRATE_CLIENT_VALUE
|
|
131
|
-
} : ce.props[import_shared_utils.HYDRATE_DIRECTIVE] = import_shared_utils.HYDRATE_CLIENT_VALUE;
|
|
132
|
-
}
|
|
133
|
-
const errMessage = (0, import_utils.createSsrErrorMessage)(specifier, err);
|
|
134
|
-
import_diagnostics.logger.warn(errMessage, err);
|
|
135
|
-
});
|
|
136
|
-
if (debug) {
|
|
137
|
-
serverDebug.message = (0, import_utils.createSsrErrorMessage)(specifiers, allErrors);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { logger } from '@lwrjs/diagnostics';
|
|
1
|
+
import { descriptions, logger, LwrStatusError, stringifyError } from '@lwrjs/diagnostics';
|
|
2
2
|
import { ViewSpan, getTracer } from '@lwrjs/instrumentation';
|
|
3
|
-
import { addHeadMarkup } from '
|
|
3
|
+
import { addHeadMarkup } from '@lwrjs/shared-utils';
|
|
4
|
+
import { mergeWarnings } from '../utils.js';
|
|
4
5
|
import { getRenderer } from '../renderer.js';
|
|
5
6
|
const NAME = 'preload-data-transformer';
|
|
6
7
|
/**
|
|
@@ -26,7 +27,7 @@ export default function preloadDataViewTransformer(_options, { config, moduleBun
|
|
|
26
27
|
return {}; // must be a CSRed rootComponent with preloadData ON
|
|
27
28
|
}
|
|
28
29
|
logger.debug({ label: NAME, message: `Preload data for root component "${rootComponent}"` });
|
|
29
|
-
const { results = {},
|
|
30
|
+
const { results = {}, warnings, bundles, } = await getTracer().trace({
|
|
30
31
|
name: ViewSpan.PreloadData,
|
|
31
32
|
attributes: { rootComponent },
|
|
32
33
|
}, async () => {
|
|
@@ -34,27 +35,33 @@ export default function preloadDataViewTransformer(_options, { config, moduleBun
|
|
|
34
35
|
if (!route) {
|
|
35
36
|
throw new Error(`Unable to resolve configuration for view: ${viewContext.view.id}`);
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
try {
|
|
39
|
+
return await getRenderer(config, moduleBundler, resourceRegistry).render({ [rootComponent]: { specifier: rootComponent, props: {} } }, route, viewContext.runtimeEnvironment, viewContext.runtimeParams);
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
// Pass redirects along
|
|
43
|
+
if (e instanceof LwrStatusError)
|
|
44
|
+
throw e;
|
|
45
|
+
// If we find errors just log as warnings since these are SSR errors for a CSR page
|
|
46
|
+
const message = descriptions.APPLICATION.PRELOAD_DATA_ERROR(rootComponent, stringifyError(e));
|
|
47
|
+
logger.warn({ label: 'preloadDataViewTransformer', message });
|
|
48
|
+
return { warnings: [message] }; // return 0 results
|
|
46
49
|
}
|
|
50
|
+
});
|
|
51
|
+
mergeWarnings(metadata, warnings);
|
|
52
|
+
const result = results[rootComponent];
|
|
53
|
+
if (!result) {
|
|
47
54
|
// Returns no data since there was an error loading modules
|
|
48
55
|
return {};
|
|
49
56
|
}
|
|
50
57
|
// Log and process the data response
|
|
51
|
-
const { props, markup, cache: { ttl } = { ttl: undefined }
|
|
58
|
+
const { props, markup, cache: { ttl } = { ttl: undefined } } = result || {};
|
|
52
59
|
logger.verbose({ label: NAME, message: 'response', additionalInfo: props });
|
|
53
|
-
markup && addHeadMarkup([
|
|
60
|
+
markup && addHeadMarkup([result.markup], stringBuilder); // add links to the <head> tag
|
|
54
61
|
metadata.serverData = metadata.serverData || {}; // create serverData if necessary
|
|
55
62
|
Object.assign(metadata.serverData, props); // add the preloaded data to serverData for serialization
|
|
56
63
|
metadata.serverBundles = bundles ? new Set([...allBundles, ...bundles]) : allBundles; // add server bundles for preloadData
|
|
57
|
-
return { cache: { ttl }
|
|
64
|
+
return { cache: { ttl } };
|
|
58
65
|
},
|
|
59
66
|
};
|
|
60
67
|
}
|
|
@@ -2,7 +2,6 @@ import type { DirectToCoreProxy } from '@lwrjs/types';
|
|
|
2
2
|
export type FetchFunction = (request: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
|
|
3
3
|
export type FetchRequestContext = {
|
|
4
4
|
host?: string;
|
|
5
|
-
requestDepth?: number;
|
|
6
5
|
headers?: HeadersInit | undefined;
|
|
7
6
|
coreProxy?: DirectToCoreProxy;
|
|
8
7
|
abortController?: AbortController;
|
|
@@ -22,7 +21,6 @@ export declare class FetchController {
|
|
|
22
21
|
private abortController;
|
|
23
22
|
private headers;
|
|
24
23
|
private host;
|
|
25
|
-
private requestDepth;
|
|
26
24
|
private coreProxy;
|
|
27
25
|
fetchEndowment: FetchFunction;
|
|
28
26
|
constructor(context: FetchRequestContext);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Pool as ClientPool } from 'undici';
|
|
2
2
|
import { logger } from '@lwrjs/diagnostics';
|
|
3
3
|
import { getTracer, ViewSpan } from '@lwrjs/instrumentation';
|
|
4
|
-
import {
|
|
5
|
-
const ROUTE_CORE_HEADER = 'X-SFDC-Route-Core';
|
|
4
|
+
import { ROUTE_CORE_HEADER, getTraceHeaders, toHostname } from '@lwrjs/shared-utils';
|
|
6
5
|
// a single Lambda only services 1 org, so this cache will not grow too large
|
|
7
6
|
const CORE_CLIENTS = new Map();
|
|
8
7
|
/**
|
|
@@ -67,10 +66,9 @@ export class FetchController {
|
|
|
67
66
|
this.noOpActivated = false;
|
|
68
67
|
};
|
|
69
68
|
this.setFetchRequestContext = (context) => {
|
|
70
|
-
const { abortController, host, headers,
|
|
69
|
+
const { abortController, host, headers, coreProxy } = context;
|
|
71
70
|
this.host = host;
|
|
72
71
|
this.headers = headers;
|
|
73
|
-
this.requestDepth = requestDepth;
|
|
74
72
|
this.coreProxy = coreProxy;
|
|
75
73
|
this.abortController = abortController;
|
|
76
74
|
};
|
|
@@ -111,20 +109,14 @@ export class FetchController {
|
|
|
111
109
|
});
|
|
112
110
|
}
|
|
113
111
|
createFetchEndowment() {
|
|
114
|
-
return (request, init) => {
|
|
115
|
-
const { host = '',
|
|
116
|
-
|
|
112
|
+
return (request, init = {}) => {
|
|
113
|
+
const { host: forwardedOrigin = '', coreProxy } = this;
|
|
114
|
+
// The Direct-to-Core proxy origin takes precedence over the Forwarded host
|
|
115
|
+
const origin = coreProxy?.origin && coreProxy.origin.startsWith('http') ? coreProxy.origin : forwardedOrigin;
|
|
117
116
|
const { finalRequest, finalUrl } = this.getFinalRequest(request, origin);
|
|
118
|
-
|
|
119
|
-
...init,
|
|
120
|
-
headers: {
|
|
121
|
-
...init?.headers,
|
|
122
|
-
[REQUEST_DEPTH_HEADER]: String(requestDepth),
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
if (coreProxy || (host && finalUrl.startsWith(host))) {
|
|
117
|
+
if (coreProxy || (forwardedOrigin && finalUrl.startsWith(forwardedOrigin))) {
|
|
126
118
|
// hint for the CDN that the request is targeted to Core
|
|
127
|
-
|
|
119
|
+
init.headers[ROUTE_CORE_HEADER] = 'true';
|
|
128
120
|
}
|
|
129
121
|
const proxyStr = coreProxy ? JSON.stringify(coreProxy) : 'none';
|
|
130
122
|
const hasCookies = this.headers && this.headers.Cookie ? 'yes' : 'no';
|
|
@@ -137,7 +129,13 @@ export class FetchController {
|
|
|
137
129
|
// Default URL to final URL if successful addInfoToSpan will replace with response.url
|
|
138
130
|
{
|
|
139
131
|
name: ViewSpan.Fetch,
|
|
140
|
-
attributes: {
|
|
132
|
+
attributes: {
|
|
133
|
+
url: finalUrl,
|
|
134
|
+
fetchType: 'cdn',
|
|
135
|
+
forwardedOrigin,
|
|
136
|
+
coreProxy: proxyStr,
|
|
137
|
+
hasCookies,
|
|
138
|
+
},
|
|
141
139
|
}, (span) => {
|
|
142
140
|
// this trace will NOT fail if fetch fails; it is meant to log the URLs fetched from the server
|
|
143
141
|
// the fetch caller (ie: getServerData) should handle the error if response.ok is false
|
|
@@ -149,28 +147,30 @@ export class FetchController {
|
|
|
149
147
|
span.setAttributes({ statusCode: res.status });
|
|
150
148
|
return res;
|
|
151
149
|
};
|
|
150
|
+
// add tracing headers based on span values
|
|
151
|
+
init.headers = { ...init.headers, ...getTraceHeaders({}, span) };
|
|
152
152
|
if (coreProxy) {
|
|
153
|
-
return this.fetchWithAgent(finalUrl,
|
|
153
|
+
return this.fetchWithAgent(finalUrl, init, forwardedOrigin, coreProxy, span)
|
|
154
154
|
.then((res) => addInfoToSpan(res))
|
|
155
155
|
.catch((err) => {
|
|
156
|
-
const { finalRequest: cdnRequest, finalUrl: cdnUrl } = this.getFinalRequest(request,
|
|
156
|
+
const { finalRequest: cdnRequest, finalUrl: cdnUrl } = this.getFinalRequest(request, forwardedOrigin);
|
|
157
157
|
logger.warn(`Fetching data directly from Core failed, retrying through CDN: ${cdnUrl} Error is: ${err.message || err}`);
|
|
158
158
|
span.setAttributes({ fetchType: 'cdnFallback' });
|
|
159
|
-
return fetch(cdnRequest,
|
|
159
|
+
return fetch(cdnRequest, init).then((res) => addInfoToSpan(res));
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
|
-
return fetch(finalRequest,
|
|
162
|
+
return fetch(finalRequest, init).then((res) => addInfoToSpan(res));
|
|
163
163
|
});
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
|
-
getFinalRequest(request,
|
|
166
|
+
getFinalRequest(request, origin) {
|
|
167
167
|
let finalRequest;
|
|
168
168
|
let finalUrl;
|
|
169
169
|
if (request instanceof Request) {
|
|
170
170
|
const curUrl = request.url;
|
|
171
|
-
// proxy relative URLs through the
|
|
171
|
+
// proxy relative URLs through the origin
|
|
172
172
|
if (curUrl.startsWith('/')) {
|
|
173
|
-
finalUrl =
|
|
173
|
+
finalUrl = origin + curUrl;
|
|
174
174
|
finalRequest = new Request(finalUrl, request);
|
|
175
175
|
}
|
|
176
176
|
else {
|
|
@@ -180,25 +180,28 @@ export class FetchController {
|
|
|
180
180
|
}
|
|
181
181
|
else {
|
|
182
182
|
const curUrl = typeof request === 'string' ? request : request.toString(); // handle string-able types, eg: URL
|
|
183
|
-
finalRequest = finalUrl = curUrl.startsWith('/') ?
|
|
183
|
+
finalRequest = finalUrl = curUrl.startsWith('/') ? origin + curUrl : curUrl; // proxy relative URLs through the origin
|
|
184
184
|
}
|
|
185
185
|
return { finalRequest, finalUrl };
|
|
186
186
|
}
|
|
187
|
-
async fetchWithAgent(rawUrl, init,
|
|
187
|
+
async fetchWithAgent(rawUrl, init, forwardedOrigin, coreProxy, span) {
|
|
188
188
|
let { origin, servername } = coreProxy;
|
|
189
|
-
const
|
|
189
|
+
const forwardedHostname = toHostname(forwardedOrigin);
|
|
190
|
+
const hostHeader = coreProxy.host ?? forwardedHostname;
|
|
190
191
|
const ENHANCED_DOMAIN_TLD = '.site.com';
|
|
191
192
|
const MY_DOMAIN_TLD = '.salesforce.com';
|
|
192
|
-
if (
|
|
193
|
+
if (forwardedHostname.endsWith(ENHANCED_DOMAIN_TLD)) {
|
|
194
|
+
const myHostname = forwardedHostname.slice(0, -ENHANCED_DOMAIN_TLD.length) + MY_DOMAIN_TLD;
|
|
193
195
|
// use the Salesforce TLD by replacing the Site TLD
|
|
194
196
|
// caller must be an Enhanced Domain on Cloudflare
|
|
195
|
-
origin =
|
|
197
|
+
origin = 'https://' + myHostname;
|
|
196
198
|
// do not use SNI with the Enhanced Domain route
|
|
197
|
-
|
|
199
|
+
// never include a protocol in the servername
|
|
200
|
+
servername = myHostname;
|
|
198
201
|
}
|
|
199
202
|
else if (origin.startsWith('.')) {
|
|
200
203
|
// use the CDN origin if the leading '.' is missing in the coreProxy origin
|
|
201
|
-
origin =
|
|
204
|
+
origin = 'https://' + forwardedHostname + origin;
|
|
202
205
|
}
|
|
203
206
|
const urlParts = new URL(rawUrl);
|
|
204
207
|
const path = urlParts.pathname + urlParts.search;
|
|
@@ -209,7 +212,7 @@ export class FetchController {
|
|
|
209
212
|
fetchType: 'direct',
|
|
210
213
|
url,
|
|
211
214
|
coreUrl: url,
|
|
212
|
-
coreHostHeader:
|
|
215
|
+
coreHostHeader: hostHeader,
|
|
213
216
|
coreServername: servername,
|
|
214
217
|
});
|
|
215
218
|
let client = CORE_CLIENTS.get(origin);
|
|
@@ -227,7 +230,7 @@ export class FetchController {
|
|
|
227
230
|
// @ts-ignore - HttpMethod not available for typecasting :|
|
|
228
231
|
method: init.method || 'GET',
|
|
229
232
|
path,
|
|
230
|
-
headers: { ...init?.headers, Host:
|
|
233
|
+
headers: { ...init?.headers, Host: hostHeader },
|
|
231
234
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
232
235
|
// @ts-ignore - not properly typed >:|
|
|
233
236
|
servername,
|
package/build/es/moduleLoader.js
CHANGED
|
@@ -13,7 +13,7 @@ export function createModuleLoader(config, resourceRegistry, bundleRegistry, run
|
|
|
13
13
|
async function createAMDModuleLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData, bootstrapConfig, abortController) {
|
|
14
14
|
// creating a render context to avoid polluting globals
|
|
15
15
|
const loaderConfig = getLoaderConfig(BOOTSTRAP_SPECIFIER, config, runtimeParams, serverData);
|
|
16
|
-
const { context, controller } = createContext(loaderConfig, runtimeParams,
|
|
16
|
+
const { context, controller } = createContext(loaderConfig, runtimeParams, abortController);
|
|
17
17
|
const contextKeyMap = new Map(Object.keys(context).map((key) => [key, true]));
|
|
18
18
|
// attaching custom init to delay bootstrap module evaluation
|
|
19
19
|
let run;
|
|
@@ -238,10 +238,9 @@ async function createESMModuleLoader() {
|
|
|
238
238
|
* @param runtimeParams - request parameters
|
|
239
239
|
* @returns a `globalThis` object
|
|
240
240
|
*/
|
|
241
|
-
function createContext(LWR, runtimeParams,
|
|
241
|
+
function createContext(LWR, runtimeParams, abortController) {
|
|
242
242
|
const fetchController = new FetchController({
|
|
243
243
|
host: runtimeParams.host,
|
|
244
|
-
requestDepth: runtimeParams.requestDepth,
|
|
245
244
|
coreProxy: runtimeParams.coreProxy,
|
|
246
245
|
abortController,
|
|
247
246
|
});
|
package/build/es/renderer.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface ServerResults extends SsrDataResponse {
|
|
|
13
13
|
}
|
|
14
14
|
export interface RenderResults {
|
|
15
15
|
results?: Record<string, ServerResults>;
|
|
16
|
-
|
|
16
|
+
warnings: (Error | string)[];
|
|
17
17
|
bundles?: Set<BundleDefinition>;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
@@ -50,7 +50,7 @@ export declare class Renderer {
|
|
|
50
50
|
* @param runtimeEnvironment - environment context
|
|
51
51
|
* @param runtimeParams - request context
|
|
52
52
|
* @param serverData - render data TODO serverData is modified (add test?)
|
|
53
|
-
* @returns render results
|
|
53
|
+
* @returns render results per component
|
|
54
54
|
*/
|
|
55
55
|
render(components: Record<string, Component>, route: NormalizedLwrRoute | NormalizedLwrErrorRoute, runtimeEnvironment: RuntimeEnvironment, runtimeParams: RuntimeParams, serverData?: ServerData, isFirstOf2PassSSR?: boolean): Promise<RenderResults>;
|
|
56
56
|
private renderComponents;
|
package/build/es/renderer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// TODO: investigate perf impact W-16056356
|
|
2
2
|
import { LRUCache } from 'lru-cache';
|
|
3
|
-
import { LwrApplicationError, descriptions, logger, stringifyError } from '@lwrjs/diagnostics';
|
|
4
|
-
import {
|
|
3
|
+
import { LwrApplicationError, LwrError, LwrStatusError, descriptions, logger, stringifyError, } from '@lwrjs/diagnostics';
|
|
4
|
+
import { REQUEST_DEPTH_HEADER, TaskPool, buildEnvironmentContext, cookieStringToObject, getCacheKeyFromJson, getFeatureFlags, getSpecifier, getTraceHeaders, isLambdaEnv, isLocalDev, moduleSpecifierToKebabCase, } from '@lwrjs/shared-utils';
|
|
5
5
|
import { ViewSpan, cacheCountStore, getTracer } from '@lwrjs/instrumentation';
|
|
6
6
|
import { getServerBootstrapServices, getRenderTimeout } from './utils.js';
|
|
7
7
|
import { createModuleLoader } from './moduleLoader.js';
|
|
@@ -61,7 +61,7 @@ export class Renderer {
|
|
|
61
61
|
* @param runtimeEnvironment - environment context
|
|
62
62
|
* @param runtimeParams - request context
|
|
63
63
|
* @param serverData - render data TODO serverData is modified (add test?)
|
|
64
|
-
* @returns render results
|
|
64
|
+
* @returns render results per component
|
|
65
65
|
*/
|
|
66
66
|
async render(components, route, runtimeEnvironment, runtimeParams, serverData = {}, isFirstOf2PassSSR) {
|
|
67
67
|
let result;
|
|
@@ -93,7 +93,7 @@ export class Renderer {
|
|
|
93
93
|
}
|
|
94
94
|
async renderComponents(components, route, runtimeEnvironment, runtimeParams, serverData = {}, isFirstOf2PassSSR = false, abortController) {
|
|
95
95
|
const results = {};
|
|
96
|
-
const
|
|
96
|
+
const warnings = [];
|
|
97
97
|
const roots = Object.keys(components);
|
|
98
98
|
const services = getServerBootstrapServices(route);
|
|
99
99
|
// For LWR@MRT, module re-evaluation is enabled by default
|
|
@@ -111,48 +111,24 @@ export class Renderer {
|
|
|
111
111
|
// When `REEVALUATE_MODULES` is enabled, we only have a single context/loader, regardless of env
|
|
112
112
|
let lwcEngine, serverBootstrapServices;
|
|
113
113
|
({ bootstrapServiceEvaluationMap, loader, lwcEngine, serverBootstrapServices } =
|
|
114
|
-
await this.createLoader(components, route, serverData, isFirstOf2PassSSR, services,
|
|
114
|
+
await this.createLoader(components, route, serverData, isFirstOf2PassSSR, services, runtimeEnvironment, runtimeParams, abortController));
|
|
115
115
|
// load root component modules (and dependencies)
|
|
116
116
|
const componentModules = await this.loadAppCode(components, route, roots, loader);
|
|
117
117
|
// Execute Services that fetch application data
|
|
118
|
-
await this.executeDataServices(components, route, serverBootstrapServices, componentModules, serverData, results,
|
|
118
|
+
await this.executeDataServices(components, route, serverBootstrapServices, componentModules, serverData, results, warnings, runtimeEnvironment, runtimeParams);
|
|
119
119
|
// exit early when preloading data
|
|
120
120
|
if (!route.bootstrap.ssr) {
|
|
121
|
-
|
|
122
|
-
return { results, errors };
|
|
123
|
-
}
|
|
124
|
-
return { results, bundles: loader?.getBundles() };
|
|
121
|
+
return { results, warnings, bundles: loader?.getBundles() };
|
|
125
122
|
}
|
|
126
123
|
// Assumed in SINGLE_RENDER mode to enable no-op during renderCmp phase.
|
|
127
124
|
loader?.getFetchController().enableNoOpFetch();
|
|
128
125
|
// render components
|
|
129
126
|
for (const component of componentModules) {
|
|
130
|
-
// skip rendering if an error has already occurred for the component
|
|
131
|
-
if (errors[component.specifier]) {
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
127
|
// eslint-disable-next-line no-await-in-loop
|
|
135
|
-
const { html
|
|
136
|
-
if (error) {
|
|
137
|
-
errors[component.specifier] = error;
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
128
|
+
const { html } = await renderToString(lwcEngine.module, component, results[component.specifier].props);
|
|
140
129
|
results[component.specifier].html = html;
|
|
141
130
|
}
|
|
142
|
-
|
|
143
|
-
if (Object.keys(errors).length) {
|
|
144
|
-
return { results, errors, bundles };
|
|
145
|
-
}
|
|
146
|
-
return { results, bundles };
|
|
147
|
-
}
|
|
148
|
-
catch (e) {
|
|
149
|
-
const error = Object(e);
|
|
150
|
-
return {
|
|
151
|
-
errors: {
|
|
152
|
-
[roots.join(',')]: error.message ?? stringifyError(e),
|
|
153
|
-
},
|
|
154
|
-
bundles: loader?.getBundles(),
|
|
155
|
-
};
|
|
131
|
+
return { results, warnings, bundles: loader?.getBundles() };
|
|
156
132
|
}
|
|
157
133
|
finally {
|
|
158
134
|
// Assumed in SINGLE_RENDER mode
|
|
@@ -205,7 +181,7 @@ export class Renderer {
|
|
|
205
181
|
/**
|
|
206
182
|
* Run bootstrap services and fetch server data
|
|
207
183
|
*/
|
|
208
|
-
async executeDataServices(components, route, serverBootstrapServices, componentModules, serverData, results,
|
|
184
|
+
async executeDataServices(components, route, serverBootstrapServices, componentModules, serverData, results, warnings, runtimeEnvironment, runtimeParams) {
|
|
209
185
|
return getTracer().trace({
|
|
210
186
|
name: ViewSpan.ExecuteServices,
|
|
211
187
|
attributes: {
|
|
@@ -237,14 +213,11 @@ export class Renderer {
|
|
|
237
213
|
crossRequestCache: this.globalCache,
|
|
238
214
|
};
|
|
239
215
|
// eslint-disable-next-line
|
|
240
|
-
const
|
|
241
|
-
if (error) {
|
|
242
|
-
errors[component.specifier] = error;
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
216
|
+
const data = await getServerData(component, context, serverData);
|
|
245
217
|
if (data) {
|
|
246
218
|
results[component.specifier] = data;
|
|
247
219
|
}
|
|
220
|
+
data.warnings && warnings.push(...data.warnings);
|
|
248
221
|
}
|
|
249
222
|
if (serverBootstrapServices) {
|
|
250
223
|
// now that we have server data, run the server data hooks
|
|
@@ -255,7 +228,7 @@ export class Renderer {
|
|
|
255
228
|
/**
|
|
256
229
|
* Create and initialize the loader and LWC engine for SSR
|
|
257
230
|
*/
|
|
258
|
-
async createLoader(components, route, serverData, isFirstOf2PassSSR, services,
|
|
231
|
+
async createLoader(components, route, serverData, isFirstOf2PassSSR, services, runtimeEnvironment, runtimeParams, abortController) {
|
|
259
232
|
return getTracer().trace({
|
|
260
233
|
name: ViewSpan.CreateLoader,
|
|
261
234
|
attributes: {
|
|
@@ -301,10 +274,7 @@ export class Renderer {
|
|
|
301
274
|
// this is where the loader and server data hooks are set
|
|
302
275
|
for (const service of serviceModules) {
|
|
303
276
|
// eslint-disable-next-line
|
|
304
|
-
|
|
305
|
-
if (error) {
|
|
306
|
-
errors[service.specifier] = error;
|
|
307
|
-
}
|
|
277
|
+
await evaluateServerBootstrapModule(service, serverBootstrapServices.serviceAPI);
|
|
308
278
|
}
|
|
309
279
|
}
|
|
310
280
|
const ret = {
|
|
@@ -331,15 +301,15 @@ export class Renderer {
|
|
|
331
301
|
resetFetchController(fetchController, runtimeParams, route, abortController) {
|
|
332
302
|
// Re-enable fetch that was disabled at the end of the previous page render via `enableFetchKillSwitch`
|
|
333
303
|
fetchController.disableFetchKillSwitch();
|
|
304
|
+
const baseHeaders = {
|
|
305
|
+
[REQUEST_DEPTH_HEADER]: runtimeParams.requestDepth ?? '0',
|
|
306
|
+
...getTraceHeaders(runtimeParams),
|
|
307
|
+
};
|
|
334
308
|
fetchController.setFetchRequestContext({
|
|
335
309
|
host: runtimeParams.host,
|
|
336
|
-
requestDepth: runtimeParams.requestDepth,
|
|
337
310
|
headers: route.bootstrap.includeCookiesForSSR
|
|
338
|
-
? {
|
|
339
|
-
|
|
340
|
-
'True-Client-IP': runtimeParams.trueClientIP,
|
|
341
|
-
}
|
|
342
|
-
: { 'True-Client-IP': runtimeParams.trueClientIP },
|
|
311
|
+
? { Cookie: runtimeParams.cookie, ...baseHeaders }
|
|
312
|
+
: baseHeaders,
|
|
343
313
|
coreProxy: runtimeParams.coreProxy,
|
|
344
314
|
abortController,
|
|
345
315
|
});
|
|
@@ -396,29 +366,44 @@ function getServerData(component, context, serverData) {
|
|
|
396
366
|
try {
|
|
397
367
|
const data = await component.module.getServerData(context);
|
|
398
368
|
Object.assign(serverData, data.props);
|
|
399
|
-
|
|
369
|
+
// TODO: delete with W-17406173 after consumers have migrated away from SsrDataResponse.status
|
|
370
|
+
const dataRes = data;
|
|
371
|
+
if (dataRes.status) {
|
|
372
|
+
const { code, location } = dataRes.status;
|
|
373
|
+
throw new LwrStatusError(undefined, code, location ? { location } : undefined);
|
|
374
|
+
}
|
|
375
|
+
return data;
|
|
400
376
|
}
|
|
401
377
|
catch (e) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
378
|
+
if (e instanceof LwrError)
|
|
379
|
+
throw e;
|
|
380
|
+
// getServerData cannot import the LwrStatusError from "@lwrjs/dianostics", since it's a client code
|
|
381
|
+
// Once getServerData is decoupled from root components (TD-0187092), then it can import from "@lwrjs/*" server modules
|
|
382
|
+
const statusError = e;
|
|
383
|
+
if (typeof statusError.status === 'number') {
|
|
384
|
+
throw new LwrStatusError(statusError.message, statusError.status, statusError.headers);
|
|
385
|
+
}
|
|
386
|
+
throw new LwrApplicationError(`Error in "getServerData" for "${component.specifier}": ${stringifyError(e)}`);
|
|
405
387
|
}
|
|
406
388
|
});
|
|
407
389
|
}
|
|
408
390
|
async function renderToString(engine, component, props) {
|
|
391
|
+
const isSSRv2 = getFeatureFlags().SSR_COMPILER_ENABLED || false;
|
|
409
392
|
return getTracer().trace({
|
|
410
393
|
name: ViewSpan.RenderComponent,
|
|
411
|
-
attributes: { specifier: component.specifier },
|
|
394
|
+
attributes: { specifier: component.specifier, isSSRv2 },
|
|
412
395
|
}, async () => {
|
|
413
396
|
try {
|
|
414
397
|
const elementName = moduleSpecifierToKebabCase(component.specifier);
|
|
415
398
|
const moduleDefault = component.module.default;
|
|
416
|
-
const html =
|
|
399
|
+
const html = isSSRv2
|
|
417
400
|
? await engine.renderComponent(elementName, moduleDefault, props, 'sync')
|
|
418
401
|
: engine.renderComponent(elementName, moduleDefault, props);
|
|
419
402
|
return { html };
|
|
420
403
|
}
|
|
421
404
|
catch (e) {
|
|
405
|
+
if (e instanceof LwrError)
|
|
406
|
+
throw e;
|
|
422
407
|
const error = Object(e);
|
|
423
408
|
// add the LWC rendering stack to the error message
|
|
424
409
|
const message = error.message ?? stringifyError(e);
|
|
@@ -428,8 +413,7 @@ async function renderToString(engine, component, props) {
|
|
|
428
413
|
'. Error was: ' +
|
|
429
414
|
message
|
|
430
415
|
: `An error occurred during server-side rendering "${component.specifier}": ` + message;
|
|
431
|
-
|
|
432
|
-
return { error: detailedMessage };
|
|
416
|
+
throw new LwrApplicationError(detailedMessage);
|
|
433
417
|
}
|
|
434
418
|
});
|
|
435
419
|
}
|
|
@@ -12,5 +12,5 @@ export declare class ServerBootstrapServices {
|
|
|
12
12
|
private registerRequestHooks;
|
|
13
13
|
}
|
|
14
14
|
export declare function createServerBootstrapServices(loader: ModuleLoader): ServerBootstrapServices;
|
|
15
|
-
export declare function evaluateServerBootstrapModule(serviceModule: any, serviceApi: ServerServiceAPI): Promise<
|
|
15
|
+
export declare function evaluateServerBootstrapModule(serviceModule: any, serviceApi: ServerServiceAPI): Promise<void>;
|
|
16
16
|
//# sourceMappingURL=serverBootstrapServices.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ViewSpan, getTracer } from '@lwrjs/instrumentation';
|
|
2
|
-
import {
|
|
2
|
+
import { LwrApplicationError, LwrError, stringifyError } from '@lwrjs/diagnostics';
|
|
3
3
|
export class ServerBootstrapServices {
|
|
4
4
|
evaluateServerDataHooks(serverData = {}) {
|
|
5
5
|
// now that we have server data, run the server data hooks
|
|
@@ -48,9 +48,9 @@ export function evaluateServerBootstrapModule(serviceModule, serviceApi) {
|
|
|
48
48
|
await serviceModule.module.default(serviceApi);
|
|
49
49
|
}
|
|
50
50
|
catch (e) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
if (e instanceof LwrError)
|
|
52
|
+
throw e;
|
|
53
|
+
throw new LwrApplicationError(`An SSR error occurred in bootstrap service "${serviceModule.specifier}": ${stringifyError(e)}`);
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
56
|
}
|