@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.
@@ -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, serverDebug} = metadata;
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
- const route = routes.find((r) => r.id === viewContext.view.id);
66
- if (!route) {
67
- throw new Error(`Unable to resolve configuration for view: ${viewContext.view.id}`);
68
- }
69
- const {
70
- results,
71
- errors,
72
- bundles: islandBundles
73
- } = await (0, import_renderer.getRenderer)(config, moduleBundler, resourceRegistry).render(ssrModules, route, viewContext.runtimeEnvironment, viewContext.runtimeParams, metadata.serverData, false);
74
- for (const root in results) {
75
- const {html, props, cache} = results[root];
76
- const {tagName, startOffset, endOffset, hydrate} = ssrModules[root];
77
- pageTtl = (0, import_shared_utils.shortestTtl)(cache?.ttl, pageTtl);
78
- if (html) {
79
- let propsAttr = "";
80
- if (hydrate) {
81
- const propsId = (0, import_utils.getPropsId)();
82
- propsAttr = ` ${import_utils.SSR_PROPS_ATTR}="${propsId}"`;
83
- serverData[propsId] = props;
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 '../utils.js';
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 = {}, errors, bundles, } = await getTracer().trace({
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
- return await getRenderer(config, moduleBundler, resourceRegistry).render({ [rootComponent]: { specifier: rootComponent, props: {} } }, route, viewContext.runtimeEnvironment, viewContext.runtimeParams);
38
- });
39
- // If we find errors just log as warnings since these are SSR errors for a CSR page
40
- if (errors) {
41
- for (const err of Object.entries(errors)) {
42
- logger.warn({
43
- label: 'preloadDataViewTransformer',
44
- message: `Unexpected error during preload data: ${err[0]}`,
45
- }, err[1]);
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 }, status } = results[rootComponent];
58
+ const { props, markup, cache: { ttl } = { ttl: undefined } } = result || {};
52
59
  logger.verbose({ label: NAME, message: 'response', additionalInfo: props });
53
- markup && addHeadMarkup([results[rootComponent]], stringBuilder); // add links to the <head> tag
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 }, status };
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 { REQUEST_DEPTH_HEADER } from '@lwrjs/shared-utils';
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, requestDepth, coreProxy } = context;
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 = '', requestDepth = 1, coreProxy } = this;
116
- const origin = coreProxy?.origin && coreProxy.origin.startsWith('http') ? coreProxy.origin : host; // The Direct-to-Core proxy origin takes precedence over the Forwarded host
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
- const finalInit = {
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
- finalInit.headers[ROUTE_CORE_HEADER] = 'true';
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: { url: finalUrl, fetchType: 'cdn', coreProxy: proxyStr, hasCookies },
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, finalInit, host, coreProxy, span)
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, host);
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, finalInit).then((res) => addInfoToSpan(res));
159
+ return fetch(cdnRequest, init).then((res) => addInfoToSpan(res));
160
160
  });
161
161
  }
162
- return fetch(finalRequest, finalInit).then((res) => addInfoToSpan(res));
162
+ return fetch(finalRequest, init).then((res) => addInfoToSpan(res));
163
163
  });
164
164
  };
165
165
  }
166
- getFinalRequest(request, host) {
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 host
171
+ // proxy relative URLs through the origin
172
172
  if (curUrl.startsWith('/')) {
173
- finalUrl = host + curUrl;
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('/') ? host + curUrl : curUrl; // proxy relative URLs through the host
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, forwardedHost, coreProxy, span) {
187
+ async fetchWithAgent(rawUrl, init, forwardedOrigin, coreProxy, span) {
188
188
  let { origin, servername } = coreProxy;
189
- const host = coreProxy.host ?? forwardedHost.replace(/^https?:\/\//, '');
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 (forwardedHost.endsWith(ENHANCED_DOMAIN_TLD)) {
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 = forwardedHost.slice(0, -ENHANCED_DOMAIN_TLD.length) + MY_DOMAIN_TLD;
197
+ origin = 'https://' + myHostname;
196
198
  // do not use SNI with the Enhanced Domain route
197
- servername = origin;
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 = forwardedHost + 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: host,
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: 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,
@@ -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, runtimeEnvironment, abortController);
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, runtimeEnvironment, abortController) {
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
  });
@@ -13,7 +13,7 @@ export interface ServerResults extends SsrDataResponse {
13
13
  }
14
14
  export interface RenderResults {
15
15
  results?: Record<string, ServerResults>;
16
- errors?: Record<string, string>;
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 and errors per component
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;
@@ -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 { buildEnvironmentContext, getCacheKeyFromJson, getSpecifier, isLambdaEnv, moduleSpecifierToKebabCase, getFeatureFlags, TaskPool, isLocalDev, cookieStringToObject, } from '@lwrjs/shared-utils';
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 and errors per component
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 errors = {};
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, errors, runtimeEnvironment, runtimeParams, abortController));
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, errors, runtimeEnvironment, runtimeParams);
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
- if (Object.keys(errors).length) {
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, error } = await renderToString(lwcEngine.module, component, results[component.specifier].props);
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
- const bundles = loader?.getBundles();
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, errors, runtimeEnvironment, runtimeParams) {
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 { data, error } = await getServerData(component, context, serverData);
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, errors, runtimeEnvironment, runtimeParams, abortController) {
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
- const error = await evaluateServerBootstrapModule(service, serverBootstrapServices.serviceAPI);
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
- Cookie: runtimeParams.cookie,
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
- return { data };
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
- const message = `Error in "getServerData" for "${component.specifier}": ${stringifyError(e)}`;
403
- logger.error(message);
404
- return { error: message };
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 = getFeatureFlags().SSR_COMPILER_ENABLED
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
- logger.error(detailedMessage);
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<string | undefined>;
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 { logger, stringifyError } from '@lwrjs/diagnostics';
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
- const message = `An SSR error occurred in bootstrap service "${serviceModule.specifier}": ${stringifyError(e)}`;
52
- logger.error(message);
53
- return message;
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
  }