@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.
- package/build/cjs/fetchController.cjs +138 -0
- package/build/cjs/moduleLoader.cjs +5 -2
- package/build/cjs/renderer.cjs +14 -15
- package/build/cjs/utils.cjs +0 -93
- package/build/es/fetchController.d.ts +47 -0
- package/build/es/fetchController.js +154 -0
- package/build/es/moduleLoader.d.ts +2 -0
- package/build/es/moduleLoader.js +6 -3
- package/build/es/renderer.d.ts +1 -2
- package/build/es/renderer.js +32 -22
- package/build/es/utils.d.ts +0 -37
- package/build/es/utils.js +1 -135
- package/package.json +8 -8
|
@@ -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
|
|
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
|
};
|
package/build/cjs/renderer.cjs
CHANGED
|
@@ -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
|
|
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()
|
|
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
|
-
|
|
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
|
|
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, {
|
package/build/cjs/utils.cjs
CHANGED
|
@@ -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>;
|
package/build/es/moduleLoader.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
}
|
package/build/es/renderer.d.ts
CHANGED
|
@@ -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 {};
|
package/build/es/renderer.js
CHANGED
|
@@ -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-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
102
|
-
const bootstrapContext = this.getCachedBootstrapContext(route, runtimeEnvironment, runtimeParams, serverData
|
|
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
|
-
//
|
|
107
|
-
loader.getFetchController()
|
|
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
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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 {
|
|
243
|
-
const contextCacheKey = getCacheKeyFromJson({ envContext, host, requestDepth });
|
|
251
|
+
const contextCacheKey = getCacheKeyFromJson({ envContext });
|
|
244
252
|
const bootstrapContext = this.contextPerEnv.get(contextCacheKey);
|
|
245
|
-
|
|
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, {
|
package/build/es/utils.d.ts
CHANGED
|
@@ -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 {
|
|
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
|
+
"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.
|
|
46
|
-
"@lwrjs/diagnostics": "0.13.
|
|
47
|
-
"@lwrjs/instrumentation": "0.13.
|
|
48
|
-
"@lwrjs/loader": "0.13.
|
|
49
|
-
"@lwrjs/shared-utils": "0.13.
|
|
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.
|
|
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": "
|
|
65
|
+
"gitHead": "c991b71930739c3ac1f411b361a09ac777f7eeb3"
|
|
66
66
|
}
|