@lwrjs/lwc-ssr 0.13.0-alpha.8 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -115,7 +115,7 @@ declare global {
115
115
 
116
116
  ### Loading data during SSR
117
117
 
118
- Many components depend on external data and resources. LWR provides a `getServerData()` hook for developers to fetch data on the server. During SSR, LWR calls the `getServerData()` hook for each **root component**, then serializes the resulting data into the page document as either [JSON](#json) or [markup](#markup).
118
+ Many components depend on external data and resources. LWR provides a [`getServerData()` hook](https://developer.salesforce.com/docs/platform/lwr/guide/lwr-enable-ssr-site.html#load-data-during-ssr) for developers to fetch data on the server. During SSR, LWR calls the `getServerData()` hook for each **root component**, then serializes the resulting data into the page document as either [JSON](#json) or [markup](#markup).
119
119
 
120
120
  If an error is thrown from `getServerData()`, rendering will fail and the server will return an HTTP 500 error for the page request. If this is undesirable, catch any errors thrown while fetching data and return default values from `getServerData()`.
121
121
 
@@ -24,6 +24,7 @@ var __toModule = (module2) => {
24
24
  // packages/@lwrjs/lwc-ssr/src/moduleLoader.ts
25
25
  __markAsModule(exports);
26
26
  __export(exports, {
27
+ FETCH_ABORT_KEY: () => FETCH_ABORT_KEY,
27
28
  createModuleLoader: () => createModuleLoader
28
29
  });
29
30
  var import_path = __toModule(require("path"));
@@ -31,34 +32,44 @@ var import_crypto = __toModule(require("crypto"));
31
32
  var import_instrumentation = __toModule(require("@lwrjs/instrumentation"));
32
33
  var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
33
34
  var import_utils = __toModule(require("./utils.cjs"));
35
+ var FETCH_ABORT_KEY = "__fetchAbortId__";
34
36
  var BOOTSTRAP_SPECIFIER = "@lwrjs/ssr-bootstrap";
35
- function createModuleLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData) {
37
+ function createModuleLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData, bootstrapConfig) {
36
38
  const createLoader = runtimeEnvironment.format === "amd" ? createAMDModuleLoader : createESMModuleLoader;
37
- return createLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData);
39
+ return createLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData, bootstrapConfig);
38
40
  }
39
- async function createAMDModuleLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData) {
41
+ async function createAMDModuleLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData, bootstrapConfig) {
40
42
  const loaderConfig = (0, import_utils.getLoaderConfig)(BOOTSTRAP_SPECIFIER, config, runtimeParams, serverData);
41
43
  const {context, controller} = createContext(loaderConfig, runtimeParams);
44
+ const contextKeyMap = new Map(Object.keys(context).map((key) => [key, true]));
42
45
  let run;
43
46
  context.LWR.customInit = async (lwr) => {
44
47
  run = lwr.initializeApp;
45
48
  };
49
+ const useEval = process.env.SSR_DEBUG === "true";
46
50
  const init = (source) => {
47
- const fn = new Function("globalThis", ...Object.keys(context), source);
48
- fn(context, ...Object.values(context));
51
+ if (!useEval) {
52
+ const fn = new Function("globalThis", ...Object.keys(context), source);
53
+ fn(context, ...Object.values(context));
54
+ } else {
55
+ ((context) => {
56
+ eval(`${Object.keys(context).reduce((c, k) => c + `const ${k} = context['${k}'];`, "globalThis=context;")} ${source}`);
57
+ })(context);
58
+ }
49
59
  };
50
- init(await (0, import_utils.getLoaderShim)(resourceRegistry, runtimeEnvironment));
60
+ init(await (0, import_utils.getLoaderShim)(resourceRegistry, runtimeEnvironment, bootstrapConfig));
51
61
  if (!run) {
52
62
  throw new Error("Failed to init loader shim: custom init not called");
53
63
  }
54
64
  const p = new Promise((resolve) => {
55
- context.LWR.define(BOOTSTRAP_SPECIFIER, [(0, import_utils.getLoaderId)(context.LWR)], (l) => resolve(l));
65
+ context.LWR.define(BOOTSTRAP_SPECIFIER, [(0, import_utils.getLoaderId)(context.LWR, bootstrapConfig)], (l) => resolve(l));
56
66
  });
57
67
  run();
58
- const {load, services} = await p;
68
+ const {load, services, clearRegistry} = await p;
59
69
  const visited = new Set(["lwc"]);
60
70
  return {
61
71
  services,
72
+ clearRegistry,
62
73
  load: async (specifier, aliases) => {
63
74
  const injectBundle = async (bundle, aliases2) => {
64
75
  if (visited.has(bundle) && specifier !== bundle) {
@@ -67,9 +78,10 @@ async function createAMDModuleLoader(config, resourceRegistry, bundleRegistry, r
67
78
  visited.add(bundle);
68
79
  const moduleId = (0, import_shared_utils.explodeSpecifier)(bundle);
69
80
  const def = await bundleRegistry.getModuleBundle(moduleId, runtimeEnvironment, runtimeParams);
70
- if (typeof def.src === "string") {
81
+ if (typeof def.src === "string" && !def.code.includes("//# sourceURL=")) {
82
+ const srcUrl = (0, import_shared_utils.isLocalDev)() ? (0, import_shared_utils.getLocalDevOverrideUrl)(config.cacheDir, moduleId.specifier, def.src) : def.src;
71
83
  def.code += `
72
- //# sourceURL=${import_path.default.resolve(def.src)}`;
84
+ //# sourceURL=${import_path.default.resolve(srcUrl)}`;
73
85
  }
74
86
  const staticImports = def.bundleRecord.imports?.map((dep) => (0, import_shared_utils.getSpecifier)(dep)) ?? [];
75
87
  const dynamicImports = def.bundleRecord.dynamicImports?.map((dep) => (0, import_shared_utils.getSpecifier)(dep)) ?? [];
@@ -101,7 +113,21 @@ async function createAMDModuleLoader(config, resourceRegistry, bundleRegistry, r
101
113
  module: mod
102
114
  };
103
115
  },
104
- getContextController: () => controller
116
+ getFetchController: () => controller,
117
+ resetGlobalContext: () => {
118
+ for (const key in context) {
119
+ if (Object.prototype.hasOwnProperty.call(context, key) && !contextKeyMap.has(key)) {
120
+ delete context[key];
121
+ }
122
+ }
123
+ context.LWR = {
124
+ ...context.LWR,
125
+ serverData: void 0
126
+ };
127
+ },
128
+ getContext: () => {
129
+ return context;
130
+ }
105
131
  };
106
132
  }
107
133
  async function createESMModuleLoader() {
@@ -125,8 +151,14 @@ function createContext(LWR, runtimeParams) {
125
151
  enableNoOpFetch: () => {
126
152
  fetchController.activateNoOp();
127
153
  },
128
- abortAllFetches: () => {
154
+ disableNoOpFetch: () => {
155
+ fetchController.deactivateNoOp();
156
+ },
157
+ enableFetchKillSwitch: () => {
129
158
  fetchController.activateKillSwitch();
159
+ },
160
+ disableFetchKillSwitch: () => {
161
+ fetchController.deactivateKillSwitch();
130
162
  }
131
163
  }
132
164
  };
@@ -27,6 +27,7 @@ __export(exports, {
27
27
  Renderer: () => Renderer,
28
28
  getRenderer: () => getRenderer
29
29
  });
30
+ var import_lru_cache = __toModule(require("lru-cache"));
30
31
  var import_diagnostics = __toModule(require("@lwrjs/diagnostics"));
31
32
  var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
32
33
  var import_instrumentation = __toModule(require("@lwrjs/instrumentation"));
@@ -45,38 +46,70 @@ var Renderer = class {
45
46
  this.config = config;
46
47
  this.bundleRegistry = bundleRegistry;
47
48
  this.resourceRegistry = resourceRegistry;
48
- }
49
- async render(components, route, runtimeEnvironment, runtimeParams, serverData = {}) {
50
- let timerId;
51
- const timeout = new Promise((_, reject) => {
52
- timerId = setTimeout(() => {
53
- reject((0, import_diagnostics.createSingleDiagnosticError)({
54
- description: import_diagnostics.descriptions.UNRESOLVABLE.SSR_TIMEOUT(route.id, (0, import_utils.getRenderTimeout)())
55
- }, import_diagnostics.LwrUnresolvableError));
56
- }, (0, import_utils.getRenderTimeout)());
49
+ this.contextPerEnv = new import_lru_cache.LRUCache({
50
+ max: 50,
51
+ dispose: () => {
52
+ import_diagnostics.logger.info("evicted bootstrap context from renderer cache");
53
+ }
57
54
  });
58
- const result = await Promise.race([
59
- timeout,
60
- this.renderComponents(components, route, runtimeEnvironment, runtimeParams, serverData)
61
- ]);
62
- clearTimeout(timerId);
55
+ const taskPoolSize = (0, import_shared_utils.getFeatureFlags)().SINGLE_RENDER_MODE ? 1 : 15;
56
+ this.pendingRenders = new import_shared_utils.TaskPool(taskPoolSize);
57
+ }
58
+ async render(components, route, runtimeEnvironment, runtimeParams, serverData = {}, isFirstOf2PassSSR) {
59
+ let result;
60
+ if ((0, import_shared_utils.isLambdaEnv)() || process.env.SSR_TIMEOUT !== void 0) {
61
+ let timerId;
62
+ const timeout = new Promise((_, reject) => {
63
+ timerId = setTimeout(() => {
64
+ reject((0, import_diagnostics.createSingleDiagnosticError)({
65
+ description: import_diagnostics.descriptions.UNRESOLVABLE.SSR_TIMEOUT(route.id, (0, import_utils.getRenderTimeout)())
66
+ }, import_diagnostics.LwrUnresolvableError));
67
+ }, (0, import_utils.getRenderTimeout)());
68
+ });
69
+ result = await this.pendingRenders.execute(async () => {
70
+ return Promise.race([
71
+ timeout,
72
+ this.renderComponents(components, route, runtimeEnvironment, runtimeParams, serverData, isFirstOf2PassSSR)
73
+ ]);
74
+ });
75
+ clearTimeout(timerId);
76
+ } else {
77
+ result = await this.pendingRenders.execute(async () => {
78
+ return this.renderComponents(components, route, runtimeEnvironment, runtimeParams, serverData, isFirstOf2PassSSR);
79
+ });
80
+ }
63
81
  return result;
64
82
  }
65
- async renderComponents(components, route, runtimeEnvironment, runtimeParams, serverData = {}) {
83
+ async renderComponents(components, route, runtimeEnvironment, runtimeParams, serverData = {}, isFirstOf2PassSSR = false) {
66
84
  const results = {};
67
85
  const errors = {};
68
86
  const roots = Object.keys(components);
69
87
  const services = (0, import_utils.getServerBootstrapServices)(route);
70
- let loader;
88
+ const reevaluateModules = (0, import_shared_utils.getFeatureFlags)().REEVALUATE_MODULES === true;
89
+ const singleRenderMode = (0, import_shared_utils.getFeatureFlags)().SINGLE_RENDER_MODE;
90
+ let loader, loaderPromise, bootstrapServiceEvaluationMap;
71
91
  try {
72
- loader = await (0, import_moduleLoader.createModuleLoader)(this.config, this.resourceRegistry, this.bundleRegistry, runtimeEnvironment, runtimeParams, serverData);
73
- const engine = await loader.load("@lwc/engine-server", ["lwc"]);
74
- const serviceModules = await Promise.all(services.map((specifier) => loader?.load(specifier)));
75
- const serverBootstrapServices = (0, import_serverBootstrapServices.createServerBootstrapServices)(loader);
76
- for (const service of serviceModules) {
77
- const error = await (0, import_serverBootstrapServices.evaluateServerBootstrapModule)(service, serverBootstrapServices.serviceAPI);
78
- if (error) {
79
- errors[service.specifier] = error;
92
+ const bootstrapContext = this.getCachedBootstrapContext(route, runtimeEnvironment, runtimeParams, serverData, reevaluateModules);
93
+ loaderPromise = bootstrapContext.loader;
94
+ bootstrapServiceEvaluationMap = bootstrapContext.bootstrapServiceEvaluationMap;
95
+ loader = await loaderPromise;
96
+ loader.getFetchController().disableFetchKillSwitch();
97
+ const engineServerSpecifier = (0, import_shared_utils.getSpecifier)({
98
+ specifier: "@lwc/engine-server",
99
+ version: route.bootstrap.lwcVersion
100
+ });
101
+ const engine = await loader.load(engineServerSpecifier, ["lwc"]);
102
+ const shouldRunBootstrapServices = !bootstrapServiceEvaluationMap?.has(route.id);
103
+ let serverBootstrapServices;
104
+ if (shouldRunBootstrapServices) {
105
+ bootstrapServiceEvaluationMap?.set(route.id, true);
106
+ const serviceModules = await Promise.all(services.map((specifier) => loader?.load(specifier)));
107
+ serverBootstrapServices = (0, import_serverBootstrapServices.createServerBootstrapServices)(loader);
108
+ for (const service of serviceModules) {
109
+ const error = await (0, import_serverBootstrapServices.evaluateServerBootstrapModule)(service, serverBootstrapServices.serviceAPI);
110
+ if (error) {
111
+ errors[service.specifier] = error;
112
+ }
80
113
  }
81
114
  }
82
115
  const componentModules = await Promise.all(roots.map((specifier) => loader?.load(specifier)));
@@ -103,14 +136,18 @@ var Renderer = class {
103
136
  results[component.specifier] = data;
104
137
  }
105
138
  }
106
- serverBootstrapServices.evaluateServerDataHooks(serverData);
139
+ if (serverBootstrapServices) {
140
+ serverBootstrapServices.evaluateServerDataHooks(serverData);
141
+ }
107
142
  if (!route.bootstrap.ssr) {
108
143
  if (Object.keys(errors).length) {
109
144
  return {results, errors};
110
145
  }
111
146
  return {results};
112
147
  }
113
- loader.getContextController().enableNoOpFetch();
148
+ if ((0, import_shared_utils.getFeatureFlags)().SINGLE_RENDER_MODE) {
149
+ loader.getFetchController().enableNoOpFetch();
150
+ }
114
151
  for (const component of componentModules) {
115
152
  if (errors[component.specifier]) {
116
153
  continue;
@@ -134,8 +171,49 @@ var Renderer = class {
134
171
  }
135
172
  };
136
173
  } finally {
137
- loader?.getContextController().abortAllFetches();
174
+ if (singleRenderMode) {
175
+ loader?.getFetchController().disableNoOpFetch();
176
+ loader?.getFetchController().enableFetchKillSwitch();
177
+ }
178
+ if (!isFirstOf2PassSSR) {
179
+ loader?.resetGlobalContext();
180
+ if (reevaluateModules) {
181
+ bootstrapServiceEvaluationMap?.delete(route.id);
182
+ loader?.clearRegistry();
183
+ }
184
+ }
185
+ }
186
+ }
187
+ getCachedBootstrapContext(route, runtimeEnvironment, runtimeParams, serverData, reevaluateModules) {
188
+ let loader, bootstrapServiceEvaluationMap;
189
+ if (reevaluateModules) {
190
+ if (!this.cachedBootstrapContext) {
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
+ const envContext = (0, import_shared_utils.buildEnvironmentContext)(runtimeParams);
199
+ const {host, requestDepth} = runtimeParams;
200
+ const contextCacheKey = (0, import_shared_utils.getCacheKeyFromJson)({envContext, host, requestDepth});
201
+ const bootstrapContext = this.contextPerEnv.get(contextCacheKey);
202
+ if (!bootstrapContext) {
203
+ loader = (0, import_moduleLoader.createModuleLoader)(this.config, this.resourceRegistry, this.bundleRegistry, runtimeEnvironment, runtimeParams, serverData, route.bootstrap);
204
+ bootstrapServiceEvaluationMap = new Map();
205
+ this.contextPerEnv.set(contextCacheKey, {
206
+ loader,
207
+ bootstrapServiceEvaluationMap
208
+ });
209
+ } else {
210
+ loader = bootstrapContext.loader;
211
+ bootstrapServiceEvaluationMap = bootstrapContext.bootstrapServiceEvaluationMap;
138
212
  }
213
+ return {
214
+ loader,
215
+ bootstrapServiceEvaluationMap
216
+ };
139
217
  }
140
218
  };
141
219
  function getServerData(component, context, serverData) {
@@ -31,7 +31,7 @@ __export(exports, {
31
31
  var import_instrumentation = __toModule(require("@lwrjs/instrumentation"));
32
32
  var import_diagnostics = __toModule(require("@lwrjs/diagnostics"));
33
33
  var ServerBootstrapServices = class {
34
- evaluateServerDataHooks(serverData) {
34
+ evaluateServerDataHooks(serverData = {}) {
35
35
  for (const serverDataHook of this.serverDataCallbacks) {
36
36
  serverDataHook({serverData});
37
37
  }
@@ -40,7 +40,12 @@ var ServerBootstrapServices = class {
40
40
  this.serverDataCallbacks = [];
41
41
  this._serviceAPI = {
42
42
  addServerDataCallback: this.registerServerDataCallbacks.bind(this),
43
- addLoaderPlugin: loader.services?.addLoaderPlugin
43
+ addLoaderPlugin: (hooks) => {
44
+ if (hooks.loadModule) {
45
+ throw new Error("`loadModule` loader hook is not supported on the server.");
46
+ }
47
+ return loader.services.addLoaderPlugin(hooks);
48
+ }
44
49
  };
45
50
  }
46
51
  get serviceAPI() {
@@ -37,7 +37,6 @@ __export(exports, {
37
37
  getRenderTimeout: () => getRenderTimeout,
38
38
  getServerBootstrapServices: () => getServerBootstrapServices
39
39
  });
40
- var import_package = __toModule(require("@lwrjs/config/package"));
41
40
  var import_diagnostics = __toModule(require("@lwrjs/diagnostics"));
42
41
  var import_instrumentation = __toModule(require("@lwrjs/instrumentation"));
43
42
  var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
@@ -53,28 +52,35 @@ function getRenderTimeout() {
53
52
  function createSsrErrorMessage(specifier, e) {
54
53
  return `Server-side rendering for "${specifier}" failed. Falling back to client-side rendering. Reason: ${(0, import_diagnostics.stringifyError)(e)}`;
55
54
  }
56
- async function getLoaderShim(resourceRegistry, runtimeEnvironment) {
57
- const specifier = (0, import_shared_utils.getFeatureFlags)().LEGACY_LOADER ? "lwr-loader-shim-legacy.bundle.min.js" : "lwr-loader-shim.bundle.min.js";
58
- const resource = await resourceRegistry.getResource({specifier}, runtimeEnvironment);
59
- if (!resource?.stream) {
55
+ async function getLoaderShim(resourceRegistry, runtimeEnvironment, bootstrapConfig) {
56
+ const {debug} = runtimeEnvironment;
57
+ const useDebug = debug && !(0, import_shared_utils.isLambdaEnv)();
58
+ const specifier = (0, import_shared_utils.getFeatureFlags)().LEGACY_LOADER ? useDebug ? "lwr-loader-shim-legacy.bundle.js" : "lwr-loader-shim-legacy.bundle.min.js" : useDebug ? "lwr-loader-shim.bundle.js" : "lwr-loader-shim.bundle.min.js";
59
+ const resource = await resourceRegistry.getResource({specifier, version: bootstrapConfig.lwrVersion}, runtimeEnvironment, {ignoreDebug: !useDebug});
60
+ if (!resource?.content && !resource?.stream) {
60
61
  throw new Error("Failed to find the loader shim");
61
62
  }
62
63
  let result = "";
63
- for await (const chunk of resource.stream()) {
64
- result += chunk;
64
+ if (resource.content) {
65
+ result = resource.content;
66
+ } else {
67
+ const stream = resource.stream();
68
+ for await (const chunk of stream) {
69
+ result += chunk;
70
+ }
65
71
  }
66
72
  result += `
67
73
  //# sourceURL=${resource.entry}`;
68
74
  return result;
69
75
  }
70
- function getLoaderId(config) {
76
+ function getLoaderId(config, bootstrapConfig) {
71
77
  if (config.requiredModules) {
72
78
  const id = config.requiredModules.find((specifier) => specifier.startsWith("lwr/loader"));
73
79
  if (id) {
74
80
  return id;
75
81
  }
76
82
  }
77
- const version = (0, import_shared_utils.normalizeVersionToUri)(import_package.lwrVersion);
83
+ const version = (0, import_shared_utils.normalizeVersionToUri)(bootstrapConfig.lwrVersion);
78
84
  return (0, import_shared_utils.getFeatureFlags)().LEGACY_LOADER ? `lwr/loaderLegacy/v/${version}` : `lwr/loader/v/${version}`;
79
85
  }
80
86
  function getLoaderConfig(bootstrapModule, config, runtimeParams, serverData) {
@@ -121,7 +127,7 @@ var FetchController = class {
121
127
  this.controllers.add(controller);
122
128
  const fetchFunction = this.noOpActivated ? this.fetchNoOp(request, updatedInit) : this.fetchEndowment(request, updatedInit);
123
129
  const fetchPromise = fetchFunction.catch((error) => {
124
- if (this.killSwitchActivated) {
130
+ if (error && error?.stack.startsWith("AbortError")) {
125
131
  return this.handleAbortError(request, error);
126
132
  } else {
127
133
  throw error;
@@ -138,9 +144,15 @@ var FetchController = class {
138
144
  });
139
145
  this.controllers.clear();
140
146
  };
147
+ this.deactivateKillSwitch = () => {
148
+ this.killSwitchActivated = false;
149
+ };
141
150
  this.activateNoOp = () => {
142
151
  this.noOpActivated = true;
143
152
  };
153
+ this.deactivateNoOp = () => {
154
+ this.noOpActivated = false;
155
+ };
144
156
  this.killSwitchActivated = false;
145
157
  this.noOpActivated = false;
146
158
  this.controllers = new Set();
@@ -59,9 +59,9 @@ var LwcViewProvider = class extends import_base_view_provider.default {
59
59
  filePath: specifier,
60
60
  viewId,
61
61
  properties: viewProperties,
62
- render: async (runtimeParams) => {
63
- const {config, moduleBundler, resourceRegistry, runtimeEnvironment} = this;
64
- const debug = runtimeParams.query?.debug !== void 0;
62
+ render: async (runtimeParams, runtimeEnvironment) => {
63
+ const {config, moduleBundler, resourceRegistry} = this;
64
+ const {debug} = runtimeEnvironment;
65
65
  const element = (0, import_shared_utils.moduleSpecifierToKebabCase)(specifier);
66
66
  return (0, import_instrumentation.getTracer)().trace({
67
67
  name: import_instrumentation.ViewSpan.RenderPage,
@@ -71,7 +71,7 @@ var LwcViewProvider = class extends import_base_view_provider.default {
71
71
  if (!route) {
72
72
  throw new Error(`Unable to resolve configuration for view: ${viewId.id}`);
73
73
  }
74
- const {results = {}, errors} = await (0, import_renderer.getRenderer)(config, moduleBundler, resourceRegistry).render({[specifier]: {specifier, props: {}}}, route, runtimeEnvironment, Object(runtimeParams));
74
+ const {results = {}, errors} = await (0, import_renderer.getRenderer)(config, moduleBundler, resourceRegistry).render({[specifier]: {specifier, props: {}}}, route, runtimeEnvironment, Object(runtimeParams), void 0, true);
75
75
  if (errors) {
76
76
  const errorString = Object.values(errors).join(", ");
77
77
  if (!debug && !(0, import_shared_utils.getFeatureFlags)().SSR_WITH_CSR_FALLBACK) {
@@ -65,7 +65,7 @@ function lwcSsrViewTransformer(options, {config, moduleBundler, resourceRegistry
65
65
  if (!route) {
66
66
  throw new Error(`Unable to resolve configuration for view: ${viewContext.view.id}`);
67
67
  }
68
- const {results, errors} = await (0, import_renderer.getRenderer)(config, moduleBundler, resourceRegistry).render(ssrModules, route, viewContext.runtimeEnvironment, viewContext.runtimeParams, metadata.serverData);
68
+ const {results, errors} = await (0, import_renderer.getRenderer)(config, moduleBundler, resourceRegistry).render(ssrModules, route, viewContext.runtimeEnvironment, viewContext.runtimeParams, metadata.serverData, false);
69
69
  for (const root in results) {
70
70
  const {html, props, cache} = results[root];
71
71
  const {tagName, startOffset, endOffset, hydrate} = ssrModules[root];
@@ -1,15 +1,21 @@
1
- import type { ClientBootstrapConfig, ProviderAppConfig, PublicModuleBundler, PublicResourceRegistry, RuntimeEnvironment, RuntimeParams, ServerData, ServerServiceAPI } from '@lwrjs/types';
1
+ import type { ClientBootstrapConfig, NormalizedLwrAppBootstrapConfig, ProviderAppConfig, PublicModuleBundler, PublicResourceRegistry, RuntimeEnvironment, RuntimeParams, ServerData, ServerServiceAPI } from '@lwrjs/types';
2
+ export declare const FETCH_ABORT_KEY = "__fetchAbortId__";
2
3
  export interface Context extends Record<string, any> {
3
4
  LWR: ClientBootstrapConfig;
4
5
  }
5
6
  export interface ContextController {
6
7
  enableNoOpFetch: () => void;
7
- abortAllFetches: () => void;
8
+ disableNoOpFetch: () => void;
9
+ enableFetchKillSwitch: () => void;
10
+ disableFetchKillSwitch: () => void;
8
11
  }
9
12
  export interface ModuleLoader {
10
13
  load: (specifier: string, aliases?: string[]) => Promise<any>;
11
- getContextController: () => ContextController;
12
- services?: ServerServiceAPI;
14
+ getFetchController: () => ContextController;
15
+ resetGlobalContext: () => void;
16
+ getContext: () => Context;
17
+ clearRegistry: Function;
18
+ services: ServerServiceAPI;
13
19
  }
14
- export declare function createModuleLoader(config: ProviderAppConfig, resourceRegistry: PublicResourceRegistry, bundleRegistry: PublicModuleBundler, runtimeEnvironment: RuntimeEnvironment, runtimeParams: RuntimeParams, serverData: ServerData): Promise<ModuleLoader>;
20
+ export declare function createModuleLoader(config: ProviderAppConfig, resourceRegistry: PublicResourceRegistry, bundleRegistry: PublicModuleBundler, runtimeEnvironment: RuntimeEnvironment, runtimeParams: RuntimeParams, serverData: ServerData, bootstrapConfig: NormalizedLwrAppBootstrapConfig): Promise<ModuleLoader>;
15
21
  //# sourceMappingURL=moduleLoader.d.ts.map
@@ -1,44 +1,56 @@
1
1
  import path from 'path';
2
2
  import crypto from 'crypto';
3
3
  import { getTracer } from '@lwrjs/instrumentation';
4
- import { explodeSpecifier, getSpecifier } from '@lwrjs/shared-utils';
4
+ import { explodeSpecifier, getLocalDevOverrideUrl, getSpecifier, isLocalDev } from '@lwrjs/shared-utils';
5
5
  import { FetchController, createFetchEndowment, getLoaderConfig, getLoaderId, getLoaderShim, } from './utils.js';
6
+ export const FETCH_ABORT_KEY = '__fetchAbortId__';
6
7
  const BOOTSTRAP_SPECIFIER = '@lwrjs/ssr-bootstrap';
7
- export function createModuleLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData) {
8
+ export function createModuleLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData, bootstrapConfig) {
8
9
  const createLoader = runtimeEnvironment.format === 'amd' ? createAMDModuleLoader : createESMModuleLoader;
9
- return createLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData);
10
+ return createLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData, bootstrapConfig);
10
11
  }
11
- async function createAMDModuleLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData) {
12
+ async function createAMDModuleLoader(config, resourceRegistry, bundleRegistry, runtimeEnvironment, runtimeParams, serverData, bootstrapConfig) {
12
13
  // creating a render context to avoid polluting globals
13
14
  const loaderConfig = getLoaderConfig(BOOTSTRAP_SPECIFIER, config, runtimeParams, serverData);
14
15
  const { context, controller } = createContext(loaderConfig, runtimeParams);
16
+ const contextKeyMap = new Map(Object.keys(context).map((key) => [key, true]));
15
17
  // attaching custom init to delay bootstrap module evaluation
16
18
  let run;
17
19
  context.LWR.customInit = async (lwr) => {
18
20
  run = lwr.initializeApp;
19
21
  };
20
22
  // instantiate modules with shadowed globals
23
+ const useEval = process.env.SSR_DEBUG === 'true';
21
24
  const init = (source) => {
22
- const fn = new Function('globalThis', ...Object.keys(context), source);
23
- fn(context, ...Object.values(context));
25
+ if (!useEval) {
26
+ const fn = new Function('globalThis', ...Object.keys(context), source);
27
+ fn(context, ...Object.values(context));
28
+ }
29
+ else {
30
+ // use eval to ensure source map line numbers are correct for debugging
31
+ ((context) => {
32
+ eval(`${Object.keys(context).reduce((c, k) => c + `const ${k} = context['${k}'];`, 'globalThis=context;')} ${source}`);
33
+ })(context);
34
+ }
24
35
  };
25
36
  // load and instantiate the loader shim
26
- init(await getLoaderShim(resourceRegistry, runtimeEnvironment));
37
+ init(await getLoaderShim(resourceRegistry, runtimeEnvironment, bootstrapConfig));
27
38
  if (!run) {
28
39
  throw new Error('Failed to init loader shim: custom init not called');
29
40
  }
30
41
  // manually define bootstrap module to export the loader
31
42
  const p = new Promise((resolve) => {
32
- context.LWR.define(BOOTSTRAP_SPECIFIER, [getLoaderId(context.LWR)], (l) => resolve(l));
43
+ context.LWR.define(BOOTSTRAP_SPECIFIER, [getLoaderId(context.LWR, bootstrapConfig)], (l) => resolve(l));
33
44
  });
34
45
  // execute the bootstrap module
35
46
  run();
36
47
  // loader API should be available after bootstrap module evaluation
37
- const { load, services } = await p;
48
+ const { load, services, clearRegistry } = await p;
38
49
  // todo: could this use the loader's module cache?
39
50
  const visited = new Set(['lwc']);
40
51
  return {
41
52
  services,
53
+ clearRegistry,
42
54
  load: async (specifier, aliases) => {
43
55
  const injectBundle = async (bundle, aliases) => {
44
56
  if (visited.has(bundle) && specifier !== bundle) {
@@ -47,10 +59,13 @@ async function createAMDModuleLoader(config, resourceRegistry, bundleRegistry, r
47
59
  visited.add(bundle);
48
60
  const moduleId = explodeSpecifier(bundle);
49
61
  const def = await bundleRegistry.getModuleBundle(moduleId, runtimeEnvironment, runtimeParams);
50
- if (typeof def.src === 'string') {
51
- def.code += `\n//# sourceURL=${path.resolve(def.src)}`;
62
+ if (typeof def.src === 'string' && !def.code.includes('//# sourceURL=')) {
63
+ // @view bundles are stored in a special location during local-dev
64
+ const srcUrl = isLocalDev()
65
+ ? getLocalDevOverrideUrl(config.cacheDir, moduleId.specifier, def.src)
66
+ : def.src;
67
+ def.code += `\n//# sourceURL=${path.resolve(srcUrl)}`;
52
68
  }
53
- // todo: could this be a loader hook?
54
69
  const staticImports = def.bundleRecord.imports?.map((dep) => getSpecifier(dep)) ?? [];
55
70
  const dynamicImports = def.bundleRecord.dynamicImports?.map((dep) => getSpecifier(dep)) ?? [];
56
71
  if (staticImports.length || dynamicImports.length) {
@@ -59,6 +74,7 @@ async function createAMDModuleLoader(config, resourceRegistry, bundleRegistry, r
59
74
  }
60
75
  // inject the bundle into the loader's module registry
61
76
  init(def.code);
77
+ // TODO: fix duplicate aliases problem
62
78
  // create aliases
63
79
  if (aliases?.length) {
64
80
  for (const alias of aliases) {
@@ -69,7 +85,7 @@ async function createAMDModuleLoader(config, resourceRegistry, bundleRegistry, r
69
85
  }), [def.id], (mod) => mod);
70
86
  }
71
87
  }
72
- // version hack
88
+ // TODO: remove this once we confirm it's no longer needed in CLWR
73
89
  if (def.specifier.startsWith('@app') || def.specifier.startsWith('@salesforce')) {
74
90
  context.LWR.define(getSpecifier(def), [def.specifier], (mod) => mod);
75
91
  }
@@ -87,7 +103,27 @@ async function createAMDModuleLoader(config, resourceRegistry, bundleRegistry, r
87
103
  module: mod,
88
104
  };
89
105
  },
90
- getContextController: () => controller,
106
+ getFetchController: () => controller,
107
+ resetGlobalContext: () => {
108
+ // Clear the context of all of the keys that may be added by module evaluation.
109
+ // Do not delete keys that we added ourselves.
110
+ for (const key in context) {
111
+ if (Object.prototype.hasOwnProperty.call(context, key) && !contextKeyMap.has(key)) {
112
+ delete context[key];
113
+ }
114
+ }
115
+ // Reset LWR globals as needed.
116
+ // Note: we only need to reset serverData today, since it is the only
117
+ // property that mutates between page renders.
118
+ context.LWR = {
119
+ ...context.LWR,
120
+ serverData: undefined,
121
+ };
122
+ },
123
+ // Note: only used for testing
124
+ getContext: () => {
125
+ return context;
126
+ },
91
127
  };
92
128
  }
93
129
  async function createESMModuleLoader() {
@@ -113,7 +149,7 @@ function createContext(LWR, runtimeParams) {
113
149
  // browser api polyfills
114
150
  crypto,
115
151
  CustomEvent: Event,
116
- // global api overrides
152
+ // global fetch api override
117
153
  fetch: fetchController.controlledFetch,
118
154
  };
119
155
  return {
@@ -122,9 +158,15 @@ function createContext(LWR, runtimeParams) {
122
158
  enableNoOpFetch: () => {
123
159
  fetchController.activateNoOp();
124
160
  },
125
- abortAllFetches: () => {
161
+ disableNoOpFetch: () => {
162
+ fetchController.deactivateNoOp();
163
+ },
164
+ enableFetchKillSwitch: () => {
126
165
  fetchController.activateKillSwitch();
127
166
  },
167
+ disableFetchKillSwitch: () => {
168
+ fetchController.deactivateKillSwitch();
169
+ },
128
170
  },
129
171
  };
130
172
  }
@@ -1,4 +1,7 @@
1
1
  import type { NormalizedLwrErrorRoute, NormalizedLwrRoute, ProviderAppConfig, PublicModuleBundler, PublicResourceRegistry, RuntimeEnvironment, RuntimeParams, ServerData, SsrDataResponse } from '@lwrjs/types';
2
+ import { LRUCache } from 'lru-cache';
3
+ import { TaskPool } from '@lwrjs/shared-utils';
4
+ import { ModuleLoader } from './moduleLoader.js';
2
5
  export interface Component {
3
6
  specifier: string;
4
7
  props?: {
@@ -20,10 +23,18 @@ export interface RenderResults {
20
23
  * @returns singleton
21
24
  */
22
25
  export declare function getRenderer(config: ProviderAppConfig, bundleRegistry: PublicModuleBundler, resourceRegistry: PublicResourceRegistry): Renderer;
26
+ interface BootstrapContext {
27
+ loader: Promise<ModuleLoader>;
28
+ bootstrapServiceEvaluationMap: Map<string, boolean>;
29
+ }
23
30
  export declare class Renderer {
24
31
  config: ProviderAppConfig;
25
32
  bundleRegistry: PublicModuleBundler;
26
33
  resourceRegistry: PublicResourceRegistry;
34
+ contextPerEnv: LRUCache<string, BootstrapContext>;
35
+ cachedBootstrapContext: BootstrapContext | undefined;
36
+ pendingRender: Promise<RenderResults> | undefined;
37
+ pendingRenders: TaskPool;
27
38
  constructor(config: ProviderAppConfig, bundleRegistry: PublicModuleBundler, resourceRegistry: PublicResourceRegistry);
28
39
  /**
29
40
  * Render components to HTML strings
@@ -41,7 +52,9 @@ export declare class Renderer {
41
52
  * @param serverData - render data TODO serverData is modified (add test?)
42
53
  * @returns render results and errors per component
43
54
  */
44
- render(components: Record<string, Component>, route: NormalizedLwrRoute | NormalizedLwrErrorRoute, runtimeEnvironment: RuntimeEnvironment, runtimeParams: RuntimeParams, serverData?: ServerData): Promise<RenderResults>;
55
+ render(components: Record<string, Component>, route: NormalizedLwrRoute | NormalizedLwrErrorRoute, runtimeEnvironment: RuntimeEnvironment, runtimeParams: RuntimeParams, serverData?: ServerData, isFirstOf2PassSSR?: boolean): Promise<RenderResults>;
45
56
  private renderComponents;
57
+ private getCachedBootstrapContext;
46
58
  }
59
+ export {};
47
60
  //# sourceMappingURL=renderer.d.ts.map
@@ -1,5 +1,7 @@
1
+ // TODO: investigate perf impact W-16056356
2
+ import { LRUCache } from 'lru-cache';
1
3
  import { LwrUnresolvableError, createSingleDiagnosticError, descriptions, logger, stringifyError, } from '@lwrjs/diagnostics';
2
- import { moduleSpecifierToKebabCase } from '@lwrjs/shared-utils';
4
+ import { buildEnvironmentContext, getCacheKeyFromJson, getSpecifier, isLambdaEnv, moduleSpecifierToKebabCase, getFeatureFlags, TaskPool, } from '@lwrjs/shared-utils';
3
5
  import { ViewSpan, getTracer } from '@lwrjs/instrumentation';
4
6
  import { getServerBootstrapServices, getRenderTimeout } from './utils.js';
5
7
  import { createModuleLoader } from './moduleLoader.js';
@@ -24,6 +26,19 @@ export class Renderer {
24
26
  this.config = config;
25
27
  this.bundleRegistry = bundleRegistry;
26
28
  this.resourceRegistry = resourceRegistry;
29
+ // Prevent number of cached contexts from growing unbounded.
30
+ // In reality, the number of env permutations should be relatively small
31
+ this.contextPerEnv = new LRUCache({
32
+ max: 50,
33
+ dispose: () => {
34
+ logger.info('evicted bootstrap context from renderer cache');
35
+ },
36
+ });
37
+ // TODO: remove the task pool altogether once W-16104831 and W-16047359 are resolved.
38
+ // Until the above issues are resolved, we can only handle one request at a time.
39
+ // Since Lambda's can only handle 1 request at a time anyways, this is fine for now.
40
+ const taskPoolSize = getFeatureFlags().SINGLE_RENDER_MODE ? 1 : 15;
41
+ this.pendingRenders = new TaskPool(taskPoolSize);
27
42
  }
28
43
  /**
29
44
  * Render components to HTML strings
@@ -41,46 +56,80 @@ export class Renderer {
41
56
  * @param serverData - render data TODO serverData is modified (add test?)
42
57
  * @returns render results and errors per component
43
58
  */
44
- async render(components, route, runtimeEnvironment, runtimeParams, serverData = {}) {
45
- let timerId;
46
- const timeout = new Promise((_, reject) => {
47
- timerId = setTimeout(() => {
48
- reject(createSingleDiagnosticError({
49
- description: descriptions.UNRESOLVABLE.SSR_TIMEOUT(route.id, getRenderTimeout()),
50
- }, LwrUnresolvableError));
51
- }, getRenderTimeout());
52
- });
53
- const result = await Promise.race([
54
- timeout,
55
- // todo: abort the render if timeout occurs
56
- this.renderComponents(components, route, runtimeEnvironment, runtimeParams, serverData),
57
- ]);
58
- clearTimeout(timerId);
59
+ async render(components, route, runtimeEnvironment, runtimeParams, serverData = {}, isFirstOf2PassSSR) {
60
+ let result;
61
+ // Only use a timeout when running in a lambda environment or if we've manually specified a timeout (for local testing purposes)
62
+ if (isLambdaEnv() || process.env.SSR_TIMEOUT !== undefined) {
63
+ let timerId;
64
+ const timeout = new Promise((_, reject) => {
65
+ timerId = setTimeout(() => {
66
+ reject(createSingleDiagnosticError({
67
+ description: descriptions.UNRESOLVABLE.SSR_TIMEOUT(route.id, getRenderTimeout()),
68
+ }, LwrUnresolvableError));
69
+ }, getRenderTimeout());
70
+ });
71
+ result = (await this.pendingRenders.execute(async () => {
72
+ return Promise.race([
73
+ timeout,
74
+ // todo: abort the render if timeout occurs
75
+ this.renderComponents(components, route, runtimeEnvironment, runtimeParams, serverData, isFirstOf2PassSSR),
76
+ ]);
77
+ }));
78
+ clearTimeout(timerId);
79
+ }
80
+ else {
81
+ result = (await this.pendingRenders.execute(async () => {
82
+ return this.renderComponents(components, route, runtimeEnvironment, runtimeParams, serverData, isFirstOf2PassSSR);
83
+ }));
84
+ }
59
85
  return result;
60
86
  }
61
- async renderComponents(components, route, runtimeEnvironment, runtimeParams, serverData = {}) {
87
+ async renderComponents(components, route, runtimeEnvironment, runtimeParams, serverData = {}, isFirstOf2PassSSR = false) {
62
88
  const results = {};
63
89
  const errors = {};
64
90
  const roots = Object.keys(components);
65
91
  const services = getServerBootstrapServices(route);
66
- let loader;
92
+ // TODO: not needed once W-16104831 is resolved
93
+ const reevaluateModules = getFeatureFlags().REEVALUATE_MODULES === true;
94
+ const singleRenderMode = getFeatureFlags().SINGLE_RENDER_MODE;
95
+ let loader, loaderPromise, bootstrapServiceEvaluationMap;
67
96
  try {
68
- loader = await createModuleLoader(this.config, this.resourceRegistry, this.bundleRegistry, runtimeEnvironment, runtimeParams, serverData);
97
+ // We want a new context (and cache it for re-use) for each LWR env context.
98
+ // E.g. if we `env.basePath: '/'` vs `env.basePath: '/foo` will result
99
+ // in the new contexts created and used for by the renderer.
100
+ //
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);
103
+ loaderPromise = bootstrapContext.loader;
104
+ bootstrapServiceEvaluationMap = bootstrapContext.bootstrapServiceEvaluationMap;
105
+ loader = await loaderPromise;
106
+ // Re-enable fetch that was disabled at the end of the previous page render via `enableFetchKillSwitch`
107
+ loader.getFetchController().disableFetchKillSwitch();
69
108
  // load and alias the LWC server engine
70
109
  // this MUST be done first in case bootstrap services depend on LWC
71
- const engine = await loader.load('@lwc/engine-server', ['lwc']);
72
- // bootstrap services MUST load/evaluate before any module resolution (except LWC)
73
- // this ensures any loader `resolveHook` is registered first
74
- const serviceModules = await Promise.all(services.map((specifier) => loader?.load(specifier)));
75
- // set up the server-side Service API for the loader
76
- const serverBootstrapServices = createServerBootstrapServices(loader);
77
- // evaluate the default function from each service module, passing in the Service API
78
- // this is where the loader and server data hooks are set
79
- for (const service of serviceModules) {
80
- // eslint-disable-next-line
81
- const error = await evaluateServerBootstrapModule(service, serverBootstrapServices.serviceAPI);
82
- if (error) {
83
- errors[service.specifier] = error;
110
+ const engineServerSpecifier = getSpecifier({
111
+ specifier: '@lwc/engine-server',
112
+ version: route.bootstrap.lwcVersion,
113
+ });
114
+ const engine = await loader.load(engineServerSpecifier, ['lwc']);
115
+ // By default, we only run bootstrap services once per page render.
116
+ const shouldRunBootstrapServices = !bootstrapServiceEvaluationMap?.has(route.id);
117
+ let serverBootstrapServices;
118
+ if (shouldRunBootstrapServices) {
119
+ bootstrapServiceEvaluationMap?.set(route.id, true);
120
+ // bootstrap services MUST load/evaluate before any module resolution (except LWC)
121
+ // this ensures any loader `resolveHook` is registered first
122
+ const serviceModules = await Promise.all(services.map((specifier) => loader?.load(specifier)));
123
+ // set up the server-side Service API for the loader
124
+ serverBootstrapServices = createServerBootstrapServices(loader);
125
+ // evaluate the default function from each service module, passing in the Service API
126
+ // this is where the loader and server data hooks are set
127
+ for (const service of serviceModules) {
128
+ // eslint-disable-next-line
129
+ const error = await evaluateServerBootstrapModule(service, serverBootstrapServices.serviceAPI);
130
+ if (error) {
131
+ errors[service.specifier] = error;
132
+ }
84
133
  }
85
134
  }
86
135
  // load root component modules (and dependencies ofc)
@@ -110,8 +159,10 @@ export class Renderer {
110
159
  results[component.specifier] = data;
111
160
  }
112
161
  }
113
- // now that we have server data, run the server data hooks
114
- serverBootstrapServices.evaluateServerDataHooks(serverData);
162
+ if (serverBootstrapServices) {
163
+ // now that we have server data, run the server data hooks
164
+ serverBootstrapServices.evaluateServerDataHooks(serverData);
165
+ }
115
166
  // exit early when preloading data
116
167
  if (!route.bootstrap.ssr) {
117
168
  if (Object.keys(errors).length) {
@@ -119,8 +170,11 @@ export class Renderer {
119
170
  }
120
171
  return { results };
121
172
  }
122
- // Disable async APIs before rendering components
123
- loader.getContextController().enableNoOpFetch();
173
+ // TODO: W-16047359 - reactivate this outside of the Lambda
174
+ if (getFeatureFlags().SINGLE_RENDER_MODE) {
175
+ // Disable async APIs before rendering components
176
+ loader.getFetchController().enableNoOpFetch();
177
+ }
124
178
  // render components
125
179
  for (const component of componentModules) {
126
180
  // skip rendering if an error has already occurred for the component
@@ -148,9 +202,62 @@ export class Renderer {
148
202
  };
149
203
  }
150
204
  finally {
151
- // activate fetch controller kill switch
152
- loader?.getContextController().abortAllFetches();
205
+ // TODO: W-16047359 - remove this check
206
+ if (singleRenderMode) {
207
+ loader?.getFetchController().disableNoOpFetch();
208
+ // activate fetch controller kill switch
209
+ loader?.getFetchController().enableFetchKillSwitch();
210
+ }
211
+ // At the end of 2-pass SSR, clean up for the next page render
212
+ if (!isFirstOf2PassSSR) {
213
+ // since we have a single global context that is maintained across page renders,
214
+ // we need to clean it up so that state such as globalThis.LWR.serverData is not preserved
215
+ loader?.resetGlobalContext();
216
+ // TODO: not needed once W-16104831 is resolved
217
+ // When `REEVALUATE_MODULES` is enabled, we clear the loader's module registry + bootstrap services
218
+ // at the end of every render so that module state is not preserved.
219
+ if (reevaluateModules) {
220
+ // clear bootstrap services so that next page render can call bootstrap services again
221
+ bootstrapServiceEvaluationMap?.delete(route.id);
222
+ loader?.clearRegistry();
223
+ }
224
+ }
225
+ }
226
+ }
227
+ getCachedBootstrapContext(route, runtimeEnvironment, runtimeParams, serverData, reevaluateModules) {
228
+ let loader, bootstrapServiceEvaluationMap;
229
+ // when `REEEVALUTE_MODULES` is true, we only have a single context/loader, regardless of the env
230
+ if (reevaluateModules) {
231
+ if (!this.cachedBootstrapContext) {
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.
241
+ const envContext = buildEnvironmentContext(runtimeParams);
242
+ const { host, requestDepth } = runtimeParams;
243
+ const contextCacheKey = getCacheKeyFromJson({ envContext, host, requestDepth });
244
+ const bootstrapContext = this.contextPerEnv.get(contextCacheKey);
245
+ if (!bootstrapContext) {
246
+ loader = createModuleLoader(this.config, this.resourceRegistry, this.bundleRegistry, runtimeEnvironment, runtimeParams, serverData, route.bootstrap);
247
+ bootstrapServiceEvaluationMap = new Map();
248
+ this.contextPerEnv.set(contextCacheKey, {
249
+ loader,
250
+ bootstrapServiceEvaluationMap,
251
+ });
252
+ }
253
+ else {
254
+ loader = bootstrapContext.loader;
255
+ bootstrapServiceEvaluationMap = bootstrapContext.bootstrapServiceEvaluationMap;
153
256
  }
257
+ return {
258
+ loader: loader,
259
+ bootstrapServiceEvaluationMap,
260
+ };
154
261
  }
155
262
  }
156
263
  /**
@@ -1,9 +1,9 @@
1
1
  import type { ServerServiceAPI, ServerData } from '@lwrjs/types';
2
- import type { ModuleLoader } from './moduleLoader';
2
+ import type { ModuleLoader } from './moduleLoader.js';
3
3
  export declare class ServerBootstrapServices {
4
4
  private serverDataCallbacks;
5
5
  _serviceAPI: ServerServiceAPI;
6
- evaluateServerDataHooks(serverData: ServerData): void;
6
+ evaluateServerDataHooks(serverData?: ServerData): void;
7
7
  constructor(loader: ModuleLoader);
8
8
  get serviceAPI(): ServerServiceAPI;
9
9
  private registerServerDataCallbacks;
@@ -1,7 +1,7 @@
1
1
  import { ViewSpan, getTracer } from '@lwrjs/instrumentation';
2
2
  import { logger, stringifyError } from '@lwrjs/diagnostics';
3
3
  export class ServerBootstrapServices {
4
- evaluateServerDataHooks(serverData) {
4
+ evaluateServerDataHooks(serverData = {}) {
5
5
  // now that we have server data, run the server data hooks
6
6
  for (const serverDataHook of this.serverDataCallbacks) {
7
7
  serverDataHook({ serverData });
@@ -11,8 +11,12 @@ export class ServerBootstrapServices {
11
11
  this.serverDataCallbacks = [];
12
12
  this._serviceAPI = {
13
13
  addServerDataCallback: this.registerServerDataCallbacks.bind(this),
14
- // TODO: SSR loader hooks - throw when `loadModule` hook is used
15
- addLoaderPlugin: loader.services?.addLoaderPlugin,
14
+ addLoaderPlugin: (hooks) => {
15
+ if (hooks.loadModule) {
16
+ throw new Error('`loadModule` loader hook is not supported on the server.');
17
+ }
18
+ return loader.services.addLoaderPlugin(hooks);
19
+ },
16
20
  };
17
21
  }
18
22
  get serviceAPI() {
@@ -1,4 +1,4 @@
1
- import type { ClientBootstrapConfig, EnvironmentContext, LwrStringBuilder, NormalizedLwrErrorRoute, NormalizedLwrRoute, ProviderAppConfig, PublicResourceRegistry, RuntimeEnvironment, RuntimeParams, ServerData, SsrDataResponse } from '@lwrjs/types';
1
+ import type { ClientBootstrapConfig, EnvironmentContext, LwrStringBuilder, NormalizedLwrAppBootstrapConfig, NormalizedLwrErrorRoute, NormalizedLwrRoute, ProviderAppConfig, PublicResourceRegistry, RuntimeEnvironment, RuntimeParams, ServerData, SsrDataResponse } from '@lwrjs/types';
2
2
  import { TraceFn } from '@lwrjs/instrumentation';
3
3
  interface ServerEnvironment extends EnvironmentContext {
4
4
  SSR: boolean;
@@ -7,8 +7,8 @@ export declare const SSR_PROPS_ATTR = "data-lwr-props-id";
7
7
  export declare function getPropsId(): string;
8
8
  export declare function getRenderTimeout(): number;
9
9
  export declare function createSsrErrorMessage(specifier: string, e: any): string;
10
- export declare function getLoaderShim(resourceRegistry: PublicResourceRegistry, runtimeEnvironment: RuntimeEnvironment): Promise<string>;
11
- export declare function getLoaderId(config: ClientBootstrapConfig): string;
10
+ export declare function getLoaderShim(resourceRegistry: PublicResourceRegistry, runtimeEnvironment: RuntimeEnvironment, bootstrapConfig: NormalizedLwrAppBootstrapConfig): Promise<string>;
11
+ export declare function getLoaderId(config: ClientBootstrapConfig, bootstrapConfig: NormalizedLwrAppBootstrapConfig): string;
12
12
  export declare function getLoaderConfig(bootstrapModule: string, config: ProviderAppConfig, runtimeParams: RuntimeParams, serverData: ServerData): ClientBootstrapConfig & {
13
13
  env: ServerEnvironment;
14
14
  };
@@ -34,11 +34,13 @@ export declare class FetchController {
34
34
  * After SSR is complete the kill switch will abort any pending fetch requests.
35
35
  */
36
36
  activateKillSwitch: () => void;
37
+ deactivateKillSwitch: () => void;
37
38
  /**
38
39
  * During SSR renderComponent (which is synchronous) Do not even call any fetch requests
39
40
  * since they would not complete before SSR is done.
40
41
  */
41
42
  activateNoOp: () => void;
43
+ deactivateNoOp: () => void;
42
44
  private handleAbortError;
43
45
  /**
44
46
  * Create a fetch API that never calls a request.
package/build/es/utils.js CHANGED
@@ -1,7 +1,6 @@
1
- import { lwrVersion } from '@lwrjs/config/package';
2
1
  import { logger, stringifyError } from '@lwrjs/diagnostics';
3
2
  import { ViewSpan } from '@lwrjs/instrumentation';
4
- import { REQUEST_DEPTH_HEADER, buildEnvironmentContext, getFeatureFlags, normalizeVersionToUri, } from '@lwrjs/shared-utils';
3
+ import { REQUEST_DEPTH_HEADER, buildEnvironmentContext, getFeatureFlags, normalizeVersionToUri, isLambdaEnv, } from '@lwrjs/shared-utils';
5
4
  const DEFAULT_SSR_TIMEOUT = 5000; // 5 seconds, override with process.env.SSR_TIMEOUT
6
5
  export const SSR_PROPS_ATTR = 'data-lwr-props-id';
7
6
  export function getPropsId() {
@@ -14,22 +13,39 @@ export function getRenderTimeout() {
14
13
  export function createSsrErrorMessage(specifier, e) {
15
14
  return `Server-side rendering for "${specifier}" failed. Falling back to client-side rendering. Reason: ${stringifyError(e)}`;
16
15
  }
17
- export async function getLoaderShim(resourceRegistry, runtimeEnvironment) {
16
+ export async function getLoaderShim(resourceRegistry, runtimeEnvironment, bootstrapConfig) {
17
+ const { debug } = runtimeEnvironment;
18
+ // debug resources are not available in deployed lambda env
19
+ const useDebug = debug && !isLambdaEnv();
18
20
  const specifier = getFeatureFlags().LEGACY_LOADER
19
- ? 'lwr-loader-shim-legacy.bundle.min.js'
20
- : 'lwr-loader-shim.bundle.min.js';
21
- const resource = await resourceRegistry.getResource({ specifier }, runtimeEnvironment);
22
- if (!resource?.stream) {
21
+ ? useDebug
22
+ ? 'lwr-loader-shim-legacy.bundle.js'
23
+ : 'lwr-loader-shim-legacy.bundle.min.js'
24
+ : useDebug
25
+ ? 'lwr-loader-shim.bundle.js'
26
+ : 'lwr-loader-shim.bundle.min.js';
27
+ const resource = await resourceRegistry.getResource({ specifier, version: bootstrapConfig.lwrVersion }, runtimeEnvironment,
28
+ // HACK: this code is tricky because resource IDs are different between prod vs debug ("lwr-loader-shim.bundle.min.js" vs "lwr-loader-shim.bundle.js").
29
+ // 1. In debug mode on Lambda (during SSR), we need to ignore runtimeEnvironment.debug because we will always ask for the prod version (lwr-loader-shim.bundle.min.js)
30
+ // 2. But when we generate the view, we can't ignore runtimeEnvironment.debug because we need the debug version of the loader shim (lwr-loader-shim.bundle.js)
31
+ { ignoreDebug: !useDebug });
32
+ if (!resource?.content && !resource?.stream) {
23
33
  throw new Error('Failed to find the loader shim');
24
34
  }
25
35
  let result = '';
26
- for await (const chunk of resource.stream()) {
27
- result += chunk;
36
+ if (resource.content) {
37
+ result = resource.content;
38
+ }
39
+ else {
40
+ const stream = resource.stream();
41
+ for await (const chunk of stream) {
42
+ result += chunk;
43
+ }
28
44
  }
29
45
  result += `\n//# sourceURL=${resource.entry}`;
30
46
  return result;
31
47
  }
32
- export function getLoaderId(config) {
48
+ export function getLoaderId(config, bootstrapConfig) {
33
49
  // TODO W-15509657 - hack: checking `requiredModules` because the loader may not be the same version as the current runtime
34
50
  if (config.requiredModules) {
35
51
  const id = config.requiredModules.find((specifier) => specifier.startsWith('lwr/loader'));
@@ -38,7 +54,7 @@ export function getLoaderId(config) {
38
54
  }
39
55
  }
40
56
  // default to the active LWR version
41
- const version = normalizeVersionToUri(lwrVersion);
57
+ const version = normalizeVersionToUri(bootstrapConfig.lwrVersion);
42
58
  return getFeatureFlags().LEGACY_LOADER ? `lwr/loaderLegacy/v/${version}` : `lwr/loader/v/${version}`;
43
59
  }
44
60
  export function getLoaderConfig(bootstrapModule, config, runtimeParams, serverData) {
@@ -101,7 +117,7 @@ export class FetchController {
101
117
  const fetchPromise = fetchFunction
102
118
  .catch((error) => {
103
119
  // Check if the error is an AbortError
104
- if (this.killSwitchActivated) {
120
+ if (error && error?.stack.startsWith('AbortError')) {
105
121
  return this.handleAbortError(request, error);
106
122
  }
107
123
  else {
@@ -124,6 +140,9 @@ export class FetchController {
124
140
  });
125
141
  this.controllers.clear(); // Clear the set as all fetch calls have been aborted
126
142
  };
143
+ this.deactivateKillSwitch = () => {
144
+ this.killSwitchActivated = false;
145
+ };
127
146
  /**
128
147
  * During SSR renderComponent (which is synchronous) Do not even call any fetch requests
129
148
  * since they would not complete before SSR is done.
@@ -131,6 +150,9 @@ export class FetchController {
131
150
  this.activateNoOp = () => {
132
151
  this.noOpActivated = true;
133
152
  };
153
+ this.deactivateNoOp = () => {
154
+ this.noOpActivated = false;
155
+ };
134
156
  this.killSwitchActivated = false;
135
157
  this.noOpActivated = false;
136
158
  this.controllers = new Set();
@@ -32,10 +32,10 @@ export default class LwcViewProvider extends BaseViewProvider {
32
32
  filePath: specifier,
33
33
  viewId,
34
34
  properties: viewProperties,
35
- render: async (runtimeParams) => {
35
+ render: async (runtimeParams, runtimeEnvironment) => {
36
36
  // SSR the root component (without passing any public properties)
37
- const { config, moduleBundler, resourceRegistry, runtimeEnvironment } = this;
38
- const debug = runtimeParams.query?.debug !== undefined;
37
+ const { config, moduleBundler, resourceRegistry } = this;
38
+ const { debug } = runtimeEnvironment;
39
39
  const element = moduleSpecifierToKebabCase(specifier);
40
40
  return getTracer().trace({
41
41
  name: ViewSpan.RenderPage,
@@ -45,7 +45,9 @@ export default class LwcViewProvider extends BaseViewProvider {
45
45
  if (!route) {
46
46
  throw new Error(`Unable to resolve configuration for view: ${viewId.id}`);
47
47
  }
48
- const { results = {}, errors } = await getRenderer(config, moduleBundler, resourceRegistry).render({ [specifier]: { specifier, props: {} } }, route, runtimeEnvironment, Object(runtimeParams));
48
+ const { results = {}, errors } = await getRenderer(config, moduleBundler, resourceRegistry).render({ [specifier]: { specifier, props: {} } }, route, runtimeEnvironment, Object(runtimeParams), undefined,
49
+ // lets the renderer know this is the first of 2-pass SSR
50
+ true);
49
51
  // Handle errors: fallback to CSR or throw
50
52
  if (errors) {
51
53
  const errorString = Object.values(errors).join(', ');
@@ -57,7 +57,7 @@ export default function lwcSsrViewTransformer(options, { config, moduleBundler,
57
57
  if (!route) {
58
58
  throw new Error(`Unable to resolve configuration for view: ${viewContext.view.id}`);
59
59
  }
60
- const { results, errors } = await getRenderer(config, moduleBundler, resourceRegistry).render(ssrModules, route, viewContext.runtimeEnvironment, viewContext.runtimeParams, metadata.serverData);
60
+ const { results, errors } = await getRenderer(config, moduleBundler, resourceRegistry).render(ssrModules, route, viewContext.runtimeEnvironment, viewContext.runtimeParams, metadata.serverData, false);
61
61
  for (const root in results) {
62
62
  const { html, props, cache } = results[root];
63
63
  const { tagName, startOffset, endOffset, hydrate } = ssrModules[root];
@@ -126,9 +126,7 @@ function handleErrors(errors, customElements, specifiers, debug, serverDebug) {
126
126
  ? (ce.props = {
127
127
  [HYDRATE_DIRECTIVE]: HYDRATE_CLIENT_VALUE,
128
128
  })
129
- : // eslint-disable-next-line @typescript-eslint/ban-ts-comment
130
- // @ts-ignore - TS thinks that props may still be undefined
131
- (ce.props[HYDRATE_DIRECTIVE] = HYDRATE_CLIENT_VALUE);
129
+ : (ce.props[HYDRATE_DIRECTIVE] = HYDRATE_CLIENT_VALUE);
132
130
  }
133
131
  // Log error with stack details
134
132
  const errMessage = createSsrErrorMessage(specifier, err);
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.13.0-alpha.8",
7
+ "version": "0.13.0",
8
8
  "homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
9
9
  "repository": {
10
10
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "scripts": {
35
35
  "build": "tsc -b",
36
- "clean": "rm -rf build node_modules",
36
+ "clean": "rimraf build node_modules",
37
37
  "test": "jest"
38
38
  },
39
39
  "files": [
@@ -42,15 +42,16 @@
42
42
  "build/**/*.d.ts"
43
43
  ],
44
44
  "dependencies": {
45
- "@lwrjs/config": "0.13.0-alpha.8",
46
- "@lwrjs/diagnostics": "0.13.0-alpha.8",
47
- "@lwrjs/instrumentation": "0.13.0-alpha.8",
48
- "@lwrjs/shared-utils": "0.13.0-alpha.8",
45
+ "@lwrjs/config": "0.13.0",
46
+ "@lwrjs/diagnostics": "0.13.0",
47
+ "@lwrjs/instrumentation": "0.13.0",
48
+ "@lwrjs/loader": "0.13.0",
49
+ "@lwrjs/shared-utils": "0.13.0",
49
50
  "fs-extra": "^11.2.0",
50
- "lru-cache": "^10.2.0"
51
+ "lru-cache": "^10.4.3"
51
52
  },
52
53
  "devDependencies": {
53
- "@lwrjs/types": "0.13.0-alpha.8",
54
+ "@lwrjs/types": "0.13.0",
54
55
  "jest": "^26.6.3",
55
56
  "mock-fs": "^5.2.0",
56
57
  "ts-jest": "^26.5.6"
@@ -61,5 +62,5 @@
61
62
  "volta": {
62
63
  "extends": "../../../package.json"
63
64
  },
64
- "gitHead": "91e731eae1457bbbebeb274fa0ea5574705c51a2"
65
+ "gitHead": "21dc6b8ffd2e633f36b46daf9e1563992c5143b9"
65
66
  }