@lwrjs/lwc-ssr 0.13.7 → 0.13.9

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.
@@ -0,0 +1,138 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, {get: all[name], enumerable: true});
11
+ };
12
+ var __exportStar = (target, module2, desc) => {
13
+ if (module2 && typeof module2 === "object" || typeof module2 === "function") {
14
+ for (let key of __getOwnPropNames(module2))
15
+ if (!__hasOwnProp.call(target, key) && key !== "default")
16
+ __defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
17
+ }
18
+ return target;
19
+ };
20
+ var __toModule = (module2) => {
21
+ return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
22
+ };
23
+
24
+ // packages/@lwrjs/lwc-ssr/src/fetchController.ts
25
+ __markAsModule(exports);
26
+ __export(exports, {
27
+ FetchController: () => FetchController
28
+ });
29
+ var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
30
+ var import_diagnostics = __toModule(require("@lwrjs/diagnostics"));
31
+ var import_instrumentation = __toModule(require("@lwrjs/instrumentation"));
32
+ var FetchController = class {
33
+ constructor(trace, host = "", requestDepth = 1) {
34
+ this.controlledFetch = (request, init) => {
35
+ if (this.killSwitchActivated) {
36
+ return this.handleAbortError(request, void 0);
37
+ }
38
+ const controller = new AbortController();
39
+ const updatedInit = {
40
+ ...init,
41
+ headers: {...init?.headers, ...this.headers},
42
+ signal: controller.signal
43
+ };
44
+ this.controllers.add(controller);
45
+ const fetchFunction = this.noOpActivated ? this.fetchNoOp(request, updatedInit) : this.fetchEndowment(request, updatedInit);
46
+ const fetchPromise = fetchFunction.catch((error) => {
47
+ if (error && error?.stack.startsWith("AbortError")) {
48
+ return this.handleAbortError(request, error);
49
+ } else {
50
+ throw error;
51
+ }
52
+ }).finally(() => {
53
+ this.controllers.delete(controller);
54
+ });
55
+ return fetchPromise;
56
+ };
57
+ this.activateKillSwitch = () => {
58
+ this.killSwitchActivated = true;
59
+ this.controllers.forEach((controller) => {
60
+ controller.abort();
61
+ });
62
+ this.controllers.clear();
63
+ };
64
+ this.deactivateKillSwitch = () => {
65
+ this.killSwitchActivated = false;
66
+ };
67
+ this.activateNoOp = () => {
68
+ this.noOpActivated = true;
69
+ };
70
+ this.deactivateNoOp = () => {
71
+ this.noOpActivated = false;
72
+ };
73
+ this.setFetchRequestContext = (context) => {
74
+ const {host, headers, requestDepth = 0} = context;
75
+ this.host = host;
76
+ this.headers = headers;
77
+ this.requestDepth = requestDepth;
78
+ };
79
+ this.killSwitchActivated = false;
80
+ this.noOpActivated = false;
81
+ this.controllers = new Set();
82
+ this.host = host;
83
+ this.requestDepth = requestDepth;
84
+ this.fetchEndowment = this.createFetchEndowment(trace);
85
+ }
86
+ handleAbortError(request, error) {
87
+ const message = `Orphaned ${String(request)} request was killed. Async processes are not supported during SSR. For more information, see: https://developer.salesforce.com/docs/platform/lwr/guide/lwr-configure-component-ssr.html.`;
88
+ import_diagnostics.logger.warn({label: `Server-side Rendering`, message}, error);
89
+ return Promise.resolve(new Response(message, {status: 500}));
90
+ }
91
+ fetchNoOp(request, init) {
92
+ return new Promise((resolve) => {
93
+ if (!init?.signal) {
94
+ resolve(this.handleAbortError(request, new Error("RequestInit was not setup as expected")));
95
+ } else if (init.signal.aborted) {
96
+ resolve(this.handleAbortError(request, new Error("Request was aborted")));
97
+ } else {
98
+ const abortHandler = (err) => {
99
+ init?.signal?.removeEventListener("abort", abortHandler);
100
+ resolve(this.handleAbortError(request, err));
101
+ };
102
+ init.signal.addEventListener("abort", abortHandler);
103
+ }
104
+ });
105
+ }
106
+ createFetchEndowment(trace) {
107
+ return (request, init) => {
108
+ const {host, requestDepth} = this;
109
+ let finalRequest;
110
+ let finalUrl;
111
+ if (request instanceof Request) {
112
+ const curUrl = request.url;
113
+ if (curUrl.startsWith("/")) {
114
+ finalUrl = host + curUrl;
115
+ finalRequest = new Request(finalUrl, request);
116
+ } else {
117
+ finalUrl = curUrl;
118
+ finalRequest = request;
119
+ }
120
+ } else {
121
+ const curUrl = typeof request === "string" ? request : request.toString();
122
+ finalRequest = finalUrl = curUrl.startsWith("/") ? host + curUrl : curUrl;
123
+ }
124
+ const finalInit = {
125
+ ...init,
126
+ headers: {
127
+ ...init?.headers,
128
+ [import_shared_utils.REQUEST_DEPTH_HEADER]: String(requestDepth)
129
+ }
130
+ };
131
+ import_diagnostics.logger.info({
132
+ label: `pre ${import_instrumentation.ViewSpan.Fetch}`,
133
+ message: `finalUrl: ${String(finalUrl)}`
134
+ });
135
+ return trace({name: import_instrumentation.ViewSpan.Fetch, attributes: {url: String(finalUrl)}}, () => fetch(finalRequest, finalInit));
136
+ };
137
+ }
138
+ };
@@ -32,6 +32,7 @@ var import_crypto = __toModule(require("crypto"));
32
32
  var import_instrumentation = __toModule(require("@lwrjs/instrumentation"));
33
33
  var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
34
34
  var import_utils = __toModule(require("./utils.cjs"));
35
+ var import_fetchController = __toModule(require("./fetchController.cjs"));
35
36
  var FETCH_ABORT_KEY = "__fetchAbortId__";
36
37
  var BOOTSTRAP_SPECIFIER = "@lwrjs/ssr-bootstrap";
37
38
  function createModuleLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData, bootstrapConfig) {
@@ -137,8 +138,7 @@ async function createESMModuleLoader() {
137
138
  function createContext(LWR, runtimeParams) {
138
139
  const tracer = (0, import_instrumentation.getTracer)();
139
140
  const trace = tracer.trace.bind(tracer);
140
- const fetchEndowment = (0, import_utils.createFetchEndowment)(trace, runtimeParams.host, runtimeParams.requestDepth);
141
- const fetchController = new import_utils.FetchController(fetchEndowment);
141
+ const fetchController = new import_fetchController.FetchController(trace, runtimeParams.host, runtimeParams.requestDepth);
142
142
  const context = {
143
143
  LWR,
144
144
  lwcRuntimeFlags: {ENABLE_WIRE_SYNC_EMIT: true},
@@ -160,6 +160,9 @@ function createContext(LWR, runtimeParams) {
160
160
  },
161
161
  disableFetchKillSwitch: () => {
162
162
  fetchController.deactivateKillSwitch();
163
+ },
164
+ setFetchRequestContext: (context2) => {
165
+ fetchController.setFetchRequestContext(context2);
163
166
  }
164
167
  }
165
168
  };
@@ -89,11 +89,12 @@ var Renderer = class {
89
89
  const singleRenderMode = (0, import_shared_utils.getFeatureFlags)().SINGLE_RENDER_MODE;
90
90
  let loader, loaderPromise, bootstrapServiceEvaluationMap;
91
91
  try {
92
- const bootstrapContext = this.getCachedBootstrapContext(route, runtimeEnvironment, runtimeParams, serverData, reevaluateModules);
92
+ const bootstrapContext = this.getCachedBootstrapContext(route, runtimeEnvironment, runtimeParams, serverData);
93
93
  loaderPromise = bootstrapContext.loader;
94
94
  bootstrapServiceEvaluationMap = bootstrapContext.bootstrapServiceEvaluationMap;
95
95
  loader = await loaderPromise;
96
- loader.getFetchController().disableFetchKillSwitch();
96
+ const fetchController = loader.getFetchController();
97
+ this.resetFetchController(fetchController, runtimeParams, route);
97
98
  const engineServerSpecifier = (0, import_shared_utils.getSpecifier)({
98
99
  specifier: "@lwc/engine-server",
99
100
  version: route.bootstrap.lwcVersion
@@ -184,22 +185,20 @@ var Renderer = class {
184
185
  }
185
186
  }
186
187
  }
187
- getCachedBootstrapContext(route, runtimeEnvironment, runtimeParams, serverData, reevaluateModules) {
188
+ resetFetchController(fetchController, runtimeParams, route) {
189
+ fetchController.disableFetchKillSwitch();
190
+ fetchController.setFetchRequestContext({
191
+ host: runtimeParams.host,
192
+ requestDepth: runtimeParams.requestDepth,
193
+ headers: void 0
194
+ });
195
+ }
196
+ getCachedBootstrapContext(route, runtimeEnvironment, runtimeParams, serverData) {
188
197
  let loader, bootstrapServiceEvaluationMap;
189
- if (reevaluateModules) {
190
- if (!this.cachedBootstrapContext || (0, import_shared_utils.isLocalDev)()) {
191
- this.cachedBootstrapContext = {
192
- loader: (0, import_moduleLoader.createModuleLoader)(this.config, this.resourceRegistry, this.bundleRegistry, runtimeEnvironment, runtimeParams, serverData, route.bootstrap),
193
- bootstrapServiceEvaluationMap: new Map()
194
- };
195
- }
196
- return this.cachedBootstrapContext;
197
- }
198
198
  const envContext = (0, import_shared_utils.buildEnvironmentContext)(runtimeParams);
199
- const {host, requestDepth} = runtimeParams;
200
- const contextCacheKey = (0, import_shared_utils.getCacheKeyFromJson)({envContext, host, requestDepth});
199
+ const contextCacheKey = (0, import_shared_utils.getCacheKeyFromJson)({envContext});
201
200
  const bootstrapContext = this.contextPerEnv.get(contextCacheKey);
202
- if (!bootstrapContext) {
201
+ if (!bootstrapContext || (0, import_shared_utils.isLocalDev)()) {
203
202
  loader = (0, import_moduleLoader.createModuleLoader)(this.config, this.resourceRegistry, this.bundleRegistry, runtimeEnvironment, runtimeParams, serverData, route.bootstrap);
204
203
  bootstrapServiceEvaluationMap = new Map();
205
204
  this.contextPerEnv.set(contextCacheKey, {
@@ -24,10 +24,8 @@ var __toModule = (module2) => {
24
24
  // packages/@lwrjs/lwc-ssr/src/utils.ts
25
25
  __markAsModule(exports);
26
26
  __export(exports, {
27
- FetchController: () => FetchController,
28
27
  SSR_PROPS_ATTR: () => SSR_PROPS_ATTR,
29
28
  addHeadMarkup: () => addHeadMarkup,
30
- createFetchEndowment: () => createFetchEndowment,
31
29
  createHeadMarkup: () => createHeadMarkup,
32
30
  createSsrErrorMarkup: () => createSsrErrorMarkup,
33
31
  createSsrErrorMessage: () => createSsrErrorMessage,
@@ -39,7 +37,6 @@ __export(exports, {
39
37
  getServerBootstrapServices: () => getServerBootstrapServices
40
38
  });
41
39
  var import_diagnostics = __toModule(require("@lwrjs/diagnostics"));
42
- var import_instrumentation = __toModule(require("@lwrjs/instrumentation"));
43
40
  var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
44
41
  var DEFAULT_SSR_TIMEOUT = 5e3;
45
42
  var SSR_PROPS_ATTR = "data-lwr-props-id";
@@ -126,96 +123,6 @@ function getServerBootstrapServices(route) {
126
123
  return acc;
127
124
  }, []);
128
125
  }
129
- var FetchController = class {
130
- constructor(fetchEndowment) {
131
- this.controlledFetch = (request, init) => {
132
- if (this.killSwitchActivated) {
133
- return this.handleAbortError(request, void 0);
134
- }
135
- const controller = new AbortController();
136
- const updatedInit = {...init, signal: controller.signal};
137
- this.controllers.add(controller);
138
- const fetchFunction = this.noOpActivated ? this.fetchNoOp(request, updatedInit) : this.fetchEndowment(request, updatedInit);
139
- const fetchPromise = fetchFunction.catch((error) => {
140
- if (error && error?.stack.startsWith("AbortError")) {
141
- return this.handleAbortError(request, error);
142
- } else {
143
- throw error;
144
- }
145
- }).finally(() => {
146
- this.controllers.delete(controller);
147
- });
148
- return fetchPromise;
149
- };
150
- this.activateKillSwitch = () => {
151
- this.killSwitchActivated = true;
152
- this.controllers.forEach((controller) => {
153
- controller.abort();
154
- });
155
- this.controllers.clear();
156
- };
157
- this.deactivateKillSwitch = () => {
158
- this.killSwitchActivated = false;
159
- };
160
- this.activateNoOp = () => {
161
- this.noOpActivated = true;
162
- };
163
- this.deactivateNoOp = () => {
164
- this.noOpActivated = false;
165
- };
166
- this.killSwitchActivated = false;
167
- this.noOpActivated = false;
168
- this.controllers = new Set();
169
- this.fetchEndowment = fetchEndowment;
170
- }
171
- handleAbortError(request, error) {
172
- const message = `Orphaned ${String(request)} request was killed. Async processes are not supported during SSR. For more information, see: https://developer.salesforce.com/docs/platform/lwr/guide/lwr-configure-component-ssr.html.`;
173
- import_diagnostics.logger.warn({label: `Server-side Rendering`, message}, error);
174
- return Promise.resolve(new Response(message, {status: 500}));
175
- }
176
- fetchNoOp(request, init) {
177
- return new Promise((resolve) => {
178
- if (!init?.signal) {
179
- resolve(this.handleAbortError(request, new Error("RequestInit was not setup as expected")));
180
- } else if (init.signal.aborted) {
181
- resolve(this.handleAbortError(request, new Error("Request was aborted")));
182
- } else {
183
- const abortHandler = (err) => {
184
- init?.signal?.removeEventListener("abort", abortHandler);
185
- resolve(this.handleAbortError(request, err));
186
- };
187
- init.signal.addEventListener("abort", abortHandler);
188
- }
189
- });
190
- }
191
- };
192
- function createFetchEndowment(trace, host = "", requestCount = 1) {
193
- return (request, init) => {
194
- let finalRequest;
195
- let finalUrl;
196
- if (request instanceof Request) {
197
- const curUrl = request.url;
198
- if (curUrl.startsWith("/")) {
199
- finalUrl = host + curUrl;
200
- finalRequest = new Request(finalUrl, request);
201
- } else {
202
- finalUrl = curUrl;
203
- finalRequest = request;
204
- }
205
- } else {
206
- const curUrl = typeof request === "string" ? request : request.toString();
207
- finalRequest = finalUrl = curUrl.startsWith("/") ? host + curUrl : curUrl;
208
- }
209
- const finalInit = {
210
- ...init,
211
- headers: {
212
- ...init?.headers,
213
- [import_shared_utils.REQUEST_DEPTH_HEADER]: String(requestCount)
214
- }
215
- };
216
- return trace({name: import_instrumentation.ViewSpan.Fetch, attributes: {url: String(finalUrl)}}, () => fetch(finalRequest, finalInit));
217
- };
218
- }
219
126
  function createMetaTags(meta) {
220
127
  return meta.reduce((metaStr, {name, content, httpEquiv}) => {
221
128
  if (!name && !content && !httpEquiv)
@@ -0,0 +1,47 @@
1
+ import { TraceFn } from '@lwrjs/instrumentation';
2
+ export type FetchFunction = (request: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
3
+ export type FetchRequestContext = {
4
+ host?: string;
5
+ requestDepth?: number;
6
+ headers?: HeadersInit | undefined;
7
+ };
8
+ /**
9
+ * During SSR a context is created with a new fetchController. At this point any fetches work as expected in this context.
10
+ *
11
+ * Right before renderComponents we call activateNoOp. At this point any new fetch calls in this context result in a no-op
12
+ * fetch waiting to be aborted.
13
+ *
14
+ * When SSR for this request is complete we call activateKillSwitch which aborts any pending fetch calls in this context.
15
+ * Any new fetch calls (i.e. from other async function calls) would be immediately aborted.
16
+ */
17
+ export declare class FetchController {
18
+ private killSwitchActivated;
19
+ private noOpActivated;
20
+ private controllers;
21
+ private headers;
22
+ private host;
23
+ private requestDepth;
24
+ fetchEndowment: FetchFunction;
25
+ constructor(trace: TraceFn<Response>, host?: string, requestDepth?: number);
26
+ controlledFetch: FetchFunction;
27
+ /**
28
+ * After SSR is complete the kill switch will abort any pending fetch requests.
29
+ */
30
+ activateKillSwitch: () => void;
31
+ deactivateKillSwitch: () => void;
32
+ /**
33
+ * During SSR renderComponent (which is synchronous) Do not even call any fetch requests
34
+ * since they would not complete before SSR is done.
35
+ */
36
+ activateNoOp: () => void;
37
+ deactivateNoOp: () => void;
38
+ setFetchRequestContext: (context: FetchRequestContext) => void;
39
+ private handleAbortError;
40
+ /**
41
+ * Create a fetch API that never calls a request.
42
+ * This is not expected to be called without an AbortController setup.
43
+ */
44
+ private fetchNoOp;
45
+ private createFetchEndowment;
46
+ }
47
+ //# sourceMappingURL=fetchController.d.ts.map
@@ -0,0 +1,154 @@
1
+ import { REQUEST_DEPTH_HEADER } from '@lwrjs/shared-utils';
2
+ import { logger } from '@lwrjs/diagnostics';
3
+ import { ViewSpan } from '@lwrjs/instrumentation';
4
+ /**
5
+ * During SSR a context is created with a new fetchController. At this point any fetches work as expected in this context.
6
+ *
7
+ * Right before renderComponents we call activateNoOp. At this point any new fetch calls in this context result in a no-op
8
+ * fetch waiting to be aborted.
9
+ *
10
+ * When SSR for this request is complete we call activateKillSwitch which aborts any pending fetch calls in this context.
11
+ * Any new fetch calls (i.e. from other async function calls) would be immediately aborted.
12
+ */
13
+ export class FetchController {
14
+ constructor(trace, host = '', requestDepth = 1) {
15
+ this.controlledFetch = (request, init) => {
16
+ if (this.killSwitchActivated) {
17
+ return this.handleAbortError(request, undefined);
18
+ }
19
+ const controller = new AbortController();
20
+ // Ensure the init object exists and then add the signal to it.
21
+ const updatedInit = {
22
+ ...init,
23
+ headers: { ...init?.headers, ...this.headers },
24
+ signal: controller.signal,
25
+ };
26
+ this.controllers.add(controller);
27
+ const fetchFunction = this.noOpActivated
28
+ ? this.fetchNoOp(request, updatedInit)
29
+ : this.fetchEndowment(request, updatedInit);
30
+ const fetchPromise = fetchFunction
31
+ .catch((error) => {
32
+ // Check if the error is an AbortError
33
+ if (error && error?.stack.startsWith('AbortError')) {
34
+ return this.handleAbortError(request, error);
35
+ }
36
+ else {
37
+ // Re-throw the error if it's not an AbortError
38
+ throw error;
39
+ }
40
+ })
41
+ .finally(() => {
42
+ this.controllers.delete(controller);
43
+ });
44
+ return fetchPromise;
45
+ };
46
+ /**
47
+ * After SSR is complete the kill switch will abort any pending fetch requests.
48
+ */
49
+ this.activateKillSwitch = () => {
50
+ this.killSwitchActivated = true;
51
+ this.controllers.forEach((controller) => {
52
+ controller.abort();
53
+ });
54
+ this.controllers.clear(); // Clear the set as all fetch calls have been aborted
55
+ };
56
+ this.deactivateKillSwitch = () => {
57
+ this.killSwitchActivated = false;
58
+ };
59
+ /**
60
+ * During SSR renderComponent (which is synchronous) Do not even call any fetch requests
61
+ * since they would not complete before SSR is done.
62
+ */
63
+ this.activateNoOp = () => {
64
+ this.noOpActivated = true;
65
+ };
66
+ this.deactivateNoOp = () => {
67
+ this.noOpActivated = false;
68
+ };
69
+ this.setFetchRequestContext = (context) => {
70
+ const { host, headers, requestDepth = 0 } = context;
71
+ this.host = host;
72
+ this.headers = headers;
73
+ this.requestDepth = requestDepth;
74
+ };
75
+ this.killSwitchActivated = false;
76
+ this.noOpActivated = false;
77
+ this.controllers = new Set();
78
+ this.host = host;
79
+ this.requestDepth = requestDepth;
80
+ this.fetchEndowment = this.createFetchEndowment(trace);
81
+ }
82
+ handleAbortError(request, error) {
83
+ const message = `Orphaned ${String(request)} request was killed. Async processes are not supported during SSR. For more information, see: https://developer.salesforce.com/docs/platform/lwr/guide/lwr-configure-component-ssr.html.`;
84
+ logger.warn({ label: `Server-side Rendering`, message }, error);
85
+ // Return a response to indicate kill switch
86
+ return Promise.resolve(new Response(message, { status: 500 }));
87
+ }
88
+ /**
89
+ * Create a fetch API that never calls a request.
90
+ * This is not expected to be called without an AbortController setup.
91
+ */
92
+ fetchNoOp(request, init) {
93
+ return new Promise((resolve) => {
94
+ if (!init?.signal) {
95
+ // This should not happen? This is only called internally to the class and we setup an abort controller.
96
+ resolve(this.handleAbortError(request, new Error('RequestInit was not setup as expected')));
97
+ }
98
+ else if (init.signal.aborted) {
99
+ // The request was already aborted go ahead and return the abort error
100
+ resolve(this.handleAbortError(request, new Error('Request was aborted')));
101
+ }
102
+ else {
103
+ // Wait until fetches are aborted to resolve with an abort error
104
+ const abortHandler = (err) => {
105
+ init?.signal?.removeEventListener('abort', abortHandler);
106
+ // Resolve the fetch
107
+ resolve(this.handleAbortError(request, err));
108
+ };
109
+ init.signal.addEventListener('abort', abortHandler);
110
+ }
111
+ });
112
+ }
113
+ createFetchEndowment(trace) {
114
+ return (request, init) => {
115
+ const { host, requestDepth } = this;
116
+ let finalRequest;
117
+ let finalUrl;
118
+ if (request instanceof Request) {
119
+ const curUrl = request.url;
120
+ // proxy relative URLs through the host
121
+ if (curUrl.startsWith('/')) {
122
+ finalUrl = host + curUrl;
123
+ finalRequest = new Request(finalUrl, request);
124
+ }
125
+ else {
126
+ finalUrl = curUrl;
127
+ finalRequest = request;
128
+ }
129
+ }
130
+ else {
131
+ const curUrl = typeof request === 'string' ? request : request.toString(); // handle string-able types, eg: URL
132
+ finalRequest = finalUrl = curUrl.startsWith('/') ? host + curUrl : curUrl; // proxy relative URLs through the host
133
+ }
134
+ const finalInit = {
135
+ ...init,
136
+ headers: {
137
+ ...init?.headers,
138
+ [REQUEST_DEPTH_HEADER]: String(requestDepth),
139
+ },
140
+ };
141
+ logger.info({
142
+ label: `pre ${ViewSpan.Fetch}`,
143
+ message: `finalUrl: ${String(finalUrl)}`,
144
+ });
145
+ return trace({ name: ViewSpan.Fetch, attributes: { url: String(finalUrl) } }, () =>
146
+ // this trace will NOT fail if fetch fails; it is meant to log the URLs fetched from the server
147
+ // the fetch caller (ie: getServerData) should handle the error if response.ok is false
148
+ // if the caller throws the fetch error, then it will be surfaced in the PARENT traces:
149
+ // lwr.view.ssr.fetch (this) > lwr.view.ssr > lwr.view.ssr.island > lwr.view.render > lwr.handle.view
150
+ fetch(finalRequest, finalInit));
151
+ };
152
+ }
153
+ }
154
+ //# sourceMappingURL=fetchController.js.map
@@ -1,4 +1,5 @@
1
1
  import type { ClientBootstrapConfig, NormalizedLwrAppBootstrapConfig, ProviderAppConfig, PublicModuleBundler, PublicResourceRegistry, RuntimeEnvironment, RuntimeParams, ServerData, ServerServiceAPI } from '@lwrjs/types';
2
+ import type { FetchRequestContext } from './fetchController.js';
2
3
  export declare const FETCH_ABORT_KEY = "__fetchAbortId__";
3
4
  export interface Context extends Record<string, any> {
4
5
  LWR: ClientBootstrapConfig;
@@ -8,6 +9,7 @@ export interface ContextController {
8
9
  disableNoOpFetch: () => void;
9
10
  enableFetchKillSwitch: () => void;
10
11
  disableFetchKillSwitch: () => void;
12
+ setFetchRequestContext: (context: FetchRequestContext) => void;
11
13
  }
12
14
  export interface ModuleLoader {
13
15
  load: (specifier: string, aliases?: string[]) => Promise<any>;
@@ -2,7 +2,8 @@ import path from 'path';
2
2
  import crypto from 'crypto';
3
3
  import { getTracer } from '@lwrjs/instrumentation';
4
4
  import { explodeSpecifier, getSpecifier } from '@lwrjs/shared-utils';
5
- import { FetchController, createFetchEndowment, getLoaderConfig, getLoaderId, getLoaderShim, } from './utils.js';
5
+ import { getLoaderConfig, getLoaderId, getLoaderShim } from './utils.js';
6
+ import { FetchController } from './fetchController.js';
6
7
  export const FETCH_ABORT_KEY = '__fetchAbortId__';
7
8
  const BOOTSTRAP_SPECIFIER = '@lwrjs/ssr-bootstrap';
8
9
  export function createModuleLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData, bootstrapConfig) {
@@ -141,8 +142,7 @@ function createContext(LWR, runtimeParams) {
141
142
  // todo: fetch endowment should consume tracer directly
142
143
  const tracer = getTracer();
143
144
  const trace = tracer.trace.bind(tracer);
144
- const fetchEndowment = createFetchEndowment(trace, runtimeParams.host, runtimeParams.requestDepth);
145
- const fetchController = new FetchController(fetchEndowment);
145
+ const fetchController = new FetchController(trace, runtimeParams.host, runtimeParams.requestDepth);
146
146
  const context = {
147
147
  LWR,
148
148
  lwcRuntimeFlags: { ENABLE_WIRE_SYNC_EMIT: true },
@@ -167,6 +167,9 @@ function createContext(LWR, runtimeParams) {
167
167
  disableFetchKillSwitch: () => {
168
168
  fetchController.deactivateKillSwitch();
169
169
  },
170
+ setFetchRequestContext: (context) => {
171
+ fetchController.setFetchRequestContext(context);
172
+ },
170
173
  },
171
174
  };
172
175
  }
@@ -32,8 +32,6 @@ export declare class Renderer {
32
32
  bundleRegistry: PublicModuleBundler;
33
33
  resourceRegistry: PublicResourceRegistry;
34
34
  contextPerEnv: LRUCache<string, BootstrapContext>;
35
- cachedBootstrapContext: BootstrapContext | undefined;
36
- pendingRender: Promise<RenderResults> | undefined;
37
35
  pendingRenders: TaskPool;
38
36
  constructor(config: ProviderAppConfig, bundleRegistry: PublicModuleBundler, resourceRegistry: PublicResourceRegistry);
39
37
  /**
@@ -54,6 +52,7 @@ export declare class Renderer {
54
52
  */
55
53
  render(components: Record<string, Component>, route: NormalizedLwrRoute | NormalizedLwrErrorRoute, runtimeEnvironment: RuntimeEnvironment, runtimeParams: RuntimeParams, serverData?: ServerData, isFirstOf2PassSSR?: boolean): Promise<RenderResults>;
56
54
  private renderComponents;
55
+ private resetFetchController;
57
56
  private getCachedBootstrapContext;
58
57
  }
59
58
  export {};
@@ -34,7 +34,7 @@ export class Renderer {
34
34
  logger.info('evicted bootstrap context from renderer cache');
35
35
  },
36
36
  });
37
- // TODO: remove the task pool altogether once W-16104831 and W-16047359 are resolved.
37
+ // TODO: remove the task pool altogether once W-16047359 is resolved.
38
38
  // Until the above issues are resolved, we can only handle one request at a time.
39
39
  // Since Lambda's can only handle 1 request at a time anyways, this is fine for now.
40
40
  const taskPoolSize = getFeatureFlags().SINGLE_RENDER_MODE ? 1 : 15;
@@ -89,8 +89,11 @@ export class Renderer {
89
89
  const errors = {};
90
90
  const roots = Object.keys(components);
91
91
  const services = getServerBootstrapServices(route);
92
- // TODO: not needed once W-16104831 is resolved
92
+ // For LWR@MRT, module re-evaluation is enabled by default
93
93
  const reevaluateModules = getFeatureFlags().REEVALUATE_MODULES === true;
94
+ // `SINGLE_RENDER_MODE` forces local preview of LWR@MRT apps to only handle
95
+ // one request at a time. This is required until our fetch endowment (W-16047359) is updated
96
+ // to be able to handle multiple concurrent requests.
94
97
  const singleRenderMode = getFeatureFlags().SINGLE_RENDER_MODE;
95
98
  let loader, loaderPromise, bootstrapServiceEvaluationMap;
96
99
  try {
@@ -98,13 +101,14 @@ export class Renderer {
98
101
  // E.g. if we `env.basePath: '/'` vs `env.basePath: '/foo` will result
99
102
  // in the new contexts created and used for by the renderer.
100
103
  //
101
- // TODO: W-16104831: when `REEVALUATE_MODULES` is enabled, we only have a single context/loader, regardless of env
102
- const bootstrapContext = this.getCachedBootstrapContext(route, runtimeEnvironment, runtimeParams, serverData, reevaluateModules);
104
+ // When `REEVALUATE_MODULES` is enabled, we only have a single context/loader, regardless of env
105
+ const bootstrapContext = this.getCachedBootstrapContext(route, runtimeEnvironment, runtimeParams, serverData);
103
106
  loaderPromise = bootstrapContext.loader;
104
107
  bootstrapServiceEvaluationMap = bootstrapContext.bootstrapServiceEvaluationMap;
105
108
  loader = await loaderPromise;
106
- // Re-enable fetch that was disabled at the end of the previous page render via `enableFetchKillSwitch`
107
- loader.getFetchController().disableFetchKillSwitch();
109
+ // reset/init the fetch controller between page renders
110
+ const fetchController = loader.getFetchController();
111
+ this.resetFetchController(fetchController, runtimeParams, route);
108
112
  // load and alias the LWC server engine
109
113
  // this MUST be done first in case bootstrap services depend on LWC
110
114
  const engineServerSpecifier = getSpecifier({
@@ -213,7 +217,6 @@ export class Renderer {
213
217
  // since we have a single global context that is maintained across page renders,
214
218
  // we need to clean it up so that state such as globalThis.LWR.serverData is not preserved
215
219
  loader?.resetGlobalContext();
216
- // TODO: not needed once W-16104831 is resolved
217
220
  // When `REEVALUATE_MODULES` is enabled, we clear the loader's module registry + bootstrap services
218
221
  // at the end of every render so that module state is not preserved.
219
222
  if (reevaluateModules) {
@@ -224,25 +227,32 @@ export class Renderer {
224
227
  }
225
228
  }
226
229
  }
227
- getCachedBootstrapContext(route, runtimeEnvironment, runtimeParams, serverData, reevaluateModules) {
230
+ resetFetchController(fetchController, runtimeParams, route) {
231
+ // Re-enable fetch that was disabled at the end of the previous page render via `enableFetchKillSwitch`
232
+ fetchController.disableFetchKillSwitch();
233
+ fetchController.setFetchRequestContext({
234
+ host: runtimeParams.host,
235
+ requestDepth: runtimeParams.requestDepth,
236
+ headers: undefined,
237
+ });
238
+ }
239
+ getCachedBootstrapContext(route, runtimeEnvironment, runtimeParams, serverData) {
228
240
  let loader, bootstrapServiceEvaluationMap;
229
241
  // when `REEEVALUTE_MODULES` is true, we only have a single context/loader, regardless of the env
230
- if (reevaluateModules) {
231
- if (!this.cachedBootstrapContext || isLocalDev()) {
232
- this.cachedBootstrapContext = {
233
- loader: createModuleLoader(this.config, this.resourceRegistry, this.bundleRegistry, runtimeEnvironment, runtimeParams, serverData, route.bootstrap),
234
- bootstrapServiceEvaluationMap: new Map(),
235
- };
236
- }
237
- return this.cachedBootstrapContext;
238
- }
239
- // The cache key is derived from LWR env context AND the host header,
240
- // because we associate the host header to each fetch context.
242
+ // We need a new context everytime we change LWR contexts that modules depend on,
243
+ // because the module needs to be *re-generated* with the new context values.
244
+ //
245
+ // On the Salesforce platform, this is *only* needed for label modules (depends on runtimeParams.locale),
246
+ // and the `lwr/environment` module (depends on multiple runtimeParams).
247
+ //
248
+ // North star, we want to avoid runtime generated modules. Do NOT add additional dependencies
249
+ // unless absolutely neccesary.
241
250
  const envContext = buildEnvironmentContext(runtimeParams);
242
- const { host, requestDepth } = runtimeParams;
243
- const contextCacheKey = getCacheKeyFromJson({ envContext, host, requestDepth });
251
+ const contextCacheKey = getCacheKeyFromJson({ envContext });
244
252
  const bootstrapContext = this.contextPerEnv.get(contextCacheKey);
245
- if (!bootstrapContext) {
253
+ // Note: we can avoid creating a new context for local dev
254
+ // if we update the loader to allow module redefines during local dev
255
+ if (!bootstrapContext || isLocalDev()) {
246
256
  loader = createModuleLoader(this.config, this.resourceRegistry, this.bundleRegistry, runtimeEnvironment, runtimeParams, serverData, route.bootstrap);
247
257
  bootstrapServiceEvaluationMap = new Map();
248
258
  this.contextPerEnv.set(contextCacheKey, {
@@ -1,5 +1,4 @@
1
1
  import type { ClientBootstrapConfig, EnvironmentContext, LwrStringBuilder, NormalizedLwrAppBootstrapConfig, NormalizedLwrErrorRoute, NormalizedLwrRoute, ProviderAppConfig, PublicResourceRegistry, RuntimeEnvironment, RuntimeParams, ServerData, SsrDataResponse } from '@lwrjs/types';
2
- import { TraceFn } from '@lwrjs/instrumentation';
3
2
  interface ServerEnvironment extends EnvironmentContext {
4
3
  SSR: boolean;
5
4
  }
@@ -14,42 +13,6 @@ export declare function getLoaderConfig(bootstrapModule: string, config: Provide
14
13
  env: ServerEnvironment;
15
14
  };
16
15
  export declare function getServerBootstrapServices(route: NormalizedLwrRoute | NormalizedLwrErrorRoute): string[];
17
- export type FetchFunction = (request: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
18
- /**
19
- * During SSR a context is created with a new fetchController. At this point any fetches work as expected in this context.
20
- *
21
- * Right before renderComponents we call activateNoOp. At this point any new fetch calls in this context result in a no-op
22
- * fetch waiting to be aborted.
23
- *
24
- * When SSR for this request is complete we call activateKillSwitch which aborts any pending fetch calls in this context.
25
- * Any new fetch calls (i.e. from other async function calls) would be immediately aborted.
26
- */
27
- export declare class FetchController {
28
- private killSwitchActivated;
29
- private noOpActivated;
30
- private controllers;
31
- private fetchEndowment;
32
- constructor(fetchEndowment: FetchFunction);
33
- controlledFetch: FetchFunction;
34
- /**
35
- * After SSR is complete the kill switch will abort any pending fetch requests.
36
- */
37
- activateKillSwitch: () => void;
38
- deactivateKillSwitch: () => void;
39
- /**
40
- * During SSR renderComponent (which is synchronous) Do not even call any fetch requests
41
- * since they would not complete before SSR is done.
42
- */
43
- activateNoOp: () => void;
44
- deactivateNoOp: () => void;
45
- private handleAbortError;
46
- /**
47
- * Create a fetch API that never calls a request.
48
- * This is not expected to be called without an AbortController setup.
49
- */
50
- private fetchNoOp;
51
- }
52
- export declare function createFetchEndowment(trace: TraceFn<Response>, host?: string, requestCount?: number): FetchFunction;
53
16
  /**
54
17
  * Serialize SsrDataResponse.markup into an HTML string
55
18
  * @param results An array of responses from getServerData hooks
package/build/es/utils.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { logger, stringifyError } from '@lwrjs/diagnostics';
2
- import { ViewSpan } from '@lwrjs/instrumentation';
3
- import { REQUEST_DEPTH_HEADER, buildEnvironmentContext, getFeatureFlags, normalizeVersionToUri, isLambdaEnv, } from '@lwrjs/shared-utils';
2
+ import { buildEnvironmentContext, getFeatureFlags, normalizeVersionToUri, isLambdaEnv, } from '@lwrjs/shared-utils';
4
3
  const DEFAULT_SSR_TIMEOUT = 5000; // 5 seconds, override with process.env.SSR_TIMEOUT
5
4
  export const SSR_PROPS_ATTR = 'data-lwr-props-id';
6
5
  export function getPropsId() {
@@ -101,139 +100,6 @@ export function getServerBootstrapServices(route) {
101
100
  return acc;
102
101
  }, []);
103
102
  }
104
- /**
105
- * During SSR a context is created with a new fetchController. At this point any fetches work as expected in this context.
106
- *
107
- * Right before renderComponents we call activateNoOp. At this point any new fetch calls in this context result in a no-op
108
- * fetch waiting to be aborted.
109
- *
110
- * When SSR for this request is complete we call activateKillSwitch which aborts any pending fetch calls in this context.
111
- * Any new fetch calls (i.e. from other async function calls) would be immediately aborted.
112
- */
113
- export class FetchController {
114
- constructor(fetchEndowment) {
115
- this.controlledFetch = (request, init) => {
116
- if (this.killSwitchActivated) {
117
- return this.handleAbortError(request, undefined);
118
- }
119
- const controller = new AbortController();
120
- // Ensure the init object exists and then add the signal to it.
121
- const updatedInit = { ...init, signal: controller.signal };
122
- this.controllers.add(controller);
123
- const fetchFunction = this.noOpActivated
124
- ? this.fetchNoOp(request, updatedInit)
125
- : this.fetchEndowment(request, updatedInit);
126
- const fetchPromise = fetchFunction
127
- .catch((error) => {
128
- // Check if the error is an AbortError
129
- if (error && error?.stack.startsWith('AbortError')) {
130
- return this.handleAbortError(request, error);
131
- }
132
- else {
133
- // Re-throw the error if it's not an AbortError
134
- throw error;
135
- }
136
- })
137
- .finally(() => {
138
- this.controllers.delete(controller);
139
- });
140
- return fetchPromise;
141
- };
142
- /**
143
- * After SSR is complete the kill switch will abort any pending fetch requests.
144
- */
145
- this.activateKillSwitch = () => {
146
- this.killSwitchActivated = true;
147
- this.controllers.forEach((controller) => {
148
- controller.abort();
149
- });
150
- this.controllers.clear(); // Clear the set as all fetch calls have been aborted
151
- };
152
- this.deactivateKillSwitch = () => {
153
- this.killSwitchActivated = false;
154
- };
155
- /**
156
- * During SSR renderComponent (which is synchronous) Do not even call any fetch requests
157
- * since they would not complete before SSR is done.
158
- */
159
- this.activateNoOp = () => {
160
- this.noOpActivated = true;
161
- };
162
- this.deactivateNoOp = () => {
163
- this.noOpActivated = false;
164
- };
165
- this.killSwitchActivated = false;
166
- this.noOpActivated = false;
167
- this.controllers = new Set();
168
- this.fetchEndowment = fetchEndowment;
169
- }
170
- handleAbortError(request, error) {
171
- const message = `Orphaned ${String(request)} request was killed. Async processes are not supported during SSR. For more information, see: https://developer.salesforce.com/docs/platform/lwr/guide/lwr-configure-component-ssr.html.`;
172
- logger.warn({ label: `Server-side Rendering`, message }, error);
173
- // Return a response to indicate kill switch
174
- return Promise.resolve(new Response(message, { status: 500 }));
175
- }
176
- /**
177
- * Create a fetch API that never calls a request.
178
- * This is not expected to be called without an AbortController setup.
179
- */
180
- fetchNoOp(request, init) {
181
- return new Promise((resolve) => {
182
- if (!init?.signal) {
183
- // This should not happen? This is only called internally to the class and we setup an abort controller.
184
- resolve(this.handleAbortError(request, new Error('RequestInit was not setup as expected')));
185
- }
186
- else if (init.signal.aborted) {
187
- // The request was already aborted go ahead and return the abort error
188
- resolve(this.handleAbortError(request, new Error('Request was aborted')));
189
- }
190
- else {
191
- // Wait until fetches are aborted to resolve with an abort error
192
- const abortHandler = (err) => {
193
- init?.signal?.removeEventListener('abort', abortHandler);
194
- // Resolve the fetch
195
- resolve(this.handleAbortError(request, err));
196
- };
197
- init.signal.addEventListener('abort', abortHandler);
198
- }
199
- });
200
- }
201
- }
202
- export function createFetchEndowment(trace, host = '', requestCount = 1) {
203
- return (request, init) => {
204
- let finalRequest;
205
- let finalUrl;
206
- if (request instanceof Request) {
207
- const curUrl = request.url;
208
- // proxy relative URLs through the host
209
- if (curUrl.startsWith('/')) {
210
- finalUrl = host + curUrl;
211
- finalRequest = new Request(finalUrl, request);
212
- }
213
- else {
214
- finalUrl = curUrl;
215
- finalRequest = request;
216
- }
217
- }
218
- else {
219
- const curUrl = typeof request === 'string' ? request : request.toString(); // handle string-able types, eg: URL
220
- finalRequest = finalUrl = curUrl.startsWith('/') ? host + curUrl : curUrl; // proxy relative URLs through the host
221
- }
222
- const finalInit = {
223
- ...init,
224
- headers: {
225
- ...init?.headers,
226
- [REQUEST_DEPTH_HEADER]: String(requestCount),
227
- },
228
- };
229
- return trace({ name: ViewSpan.Fetch, attributes: { url: String(finalUrl) } }, () =>
230
- // this trace will NOT fail if fetch fails; it is meant to log the URLs fetched from the server
231
- // the fetch caller (ie: getServerData) should handle the error if response.ok is false
232
- // if the caller throws the fetch error, then it will be surfaced in the PARENT traces:
233
- // lwr.view.ssr.fetch (this) > lwr.view.ssr > lwr.view.ssr.island > lwr.view.render > lwr.handle.view
234
- fetch(finalRequest, finalInit));
235
- };
236
- }
237
103
  /** SSR HEAD MARKUP UTILS */
238
104
  function createMetaTags(meta) {
239
105
  return meta.reduce((metaStr, { name, content, httpEquiv }) => {
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.13.7",
7
+ "version": "0.13.9",
8
8
  "homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
9
9
  "repository": {
10
10
  "type": "git",
@@ -42,16 +42,16 @@
42
42
  "build/**/*.d.ts"
43
43
  ],
44
44
  "dependencies": {
45
- "@lwrjs/config": "0.13.7",
46
- "@lwrjs/diagnostics": "0.13.7",
47
- "@lwrjs/instrumentation": "0.13.7",
48
- "@lwrjs/loader": "0.13.7",
49
- "@lwrjs/shared-utils": "0.13.7",
45
+ "@lwrjs/config": "0.13.9",
46
+ "@lwrjs/diagnostics": "0.13.9",
47
+ "@lwrjs/instrumentation": "0.13.9",
48
+ "@lwrjs/loader": "0.13.9",
49
+ "@lwrjs/shared-utils": "0.13.9",
50
50
  "fs-extra": "^11.2.0",
51
51
  "lru-cache": "^10.4.3"
52
52
  },
53
53
  "devDependencies": {
54
- "@lwrjs/types": "0.13.7",
54
+ "@lwrjs/types": "0.13.9",
55
55
  "jest": "^26.6.3",
56
56
  "memfs": "^4.9.3",
57
57
  "ts-jest": "^26.5.6"
@@ -62,5 +62,5 @@
62
62
  "volta": {
63
63
  "extends": "../../../package.json"
64
64
  },
65
- "gitHead": "216a7383b5ae5cba4274f093ff3fff5accc3405f"
65
+ "gitHead": "c991b71930739c3ac1f411b361a09ac777f7eeb3"
66
66
  }