@lwrjs/lwc-ssr 0.9.0-alpha.3 → 0.9.0-alpha.30

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
@@ -1,11 +1,34 @@
1
1
  # Server-side rendering (SSR) in LWR
2
2
 
3
+ - [Overview](#overview)
4
+ - [What is SSR?](#what-is-ssr)
5
+ - [Why use SSR?](#why-use-ssr)
6
+ - [Using SSR with LWR](#using-ssr-with-lwr)
7
+ - [Turn on SSR](#turn-on-ssr)
8
+ - [Building SSR pages](#building-ssr-pages)
9
+ - [Loading data during SSR](#loading-data-during-ssr)
10
+ - [Caching](#caching)
11
+ - [Client hydration](#client-hydration)
12
+ - [Best Practices](#best-practices)
13
+ - [SSR execution](#ssr-execution)
14
+ - [Portability](#portability)
15
+ - [Synchronous code](#synchronous-code)
16
+ - [Debugging](#debugging)
17
+ - [Debug logging]()
18
+ - [Breakpoints](#breakpoints)
19
+ - [Inspect bundles](#inspect-bundles)
20
+
3
21
  ## Overview
4
22
 
5
23
  ### What is SSR?
6
24
 
7
25
  [Lightning Web Components (LWC)](https://lwc.dev/) is a framework for creating client-side applications. However, these components can also be rendered as an HTML string on the **server**. LWR sends these strings to the client, where they can be ["hydrated"](#client-hydration) to create an interactive web app.
8
26
 
27
+ See the following RFCs from LWC:
28
+
29
+ - [LWC Server Engine](https://rfcs.lwc.dev/rfcs/lwc/0112-server-engine)
30
+ - [SSR Rehydration](https://rfcs.lwc.dev/rfcs/lwc/0117-ssr-rehydration)
31
+
9
32
  ### Why use SSR?
10
33
 
11
34
  With SSR, the browser does not need to wait for all the JavaScript to download and execute before displaying component markup. This results in faster time-to-content, especially on slower devices or internet connections. It also makes the content accessible to search engine crawlers, improving SEO.
@@ -18,11 +41,21 @@ Learn how to use SSR in your LWR apps.
18
41
 
19
42
  ### Turn on SSR
20
43
 
21
- SSR is activated on a per-route basis by changing `bootstrap.ssr` to `true`:
44
+ The SSR `moduleProvider` and `viewTransformer` must be pulled into the app to handle the SSR processing. Then SSR can be activated on a per-route basis by changing `bootstrap.ssr` to `true`.
22
45
 
23
46
  ```json
24
47
  // my-app/lwr.config.json
25
48
  {
49
+ "moduleProviders": [
50
+ "@lwrjs/app-service/moduleProvider",
51
+ "@lwrjs/lwc-ssr/moduleProvider", // create modules which perform SSR
52
+ "@lwrjs/lwc-module-provider",
53
+ "@lwrjs/npm-module-provider"
54
+ ],
55
+ "viewTransformers": [
56
+ "@lwrjs/base-view-transformer",
57
+ "@lwrjs/lwc-ssr/viewTransformer" // inserts SSRed HTML into a page document
58
+ ],
26
59
  "routes": [
27
60
  {
28
61
  "id": "ssr-page",
@@ -39,13 +72,15 @@ SSR is activated on a per-route basis by changing `bootstrap.ssr` to `true`:
39
72
  }
40
73
  ```
41
74
 
75
+ > See an example app which uses SSR in the [LWR recipes repository](https://github.com/salesforce-experience-platform-emu/lwr-recipes/tree/main/packages/ssr).
76
+
42
77
  ### Building SSR pages
43
78
 
44
79
  When a route with `bootstrap.ssr` is requested, LWR will use [LWC'S `renderComponent()` function](https://rfcs.lwc.dev/rfcs/lwc/0112-server-engine) to SSR each **root component** on the page. This is done whether the page is generated at runtime, or pre-built using `generateStaticSite()`.
45
80
 
46
- > A "root component" is any lwc in an app route's [content template, layout template](https://github.com/salesforce/lwr-recipes/tree/main/packages/templating#templates), or [`rootComponent` configuration](https://github.com/salesforce/lwr-recipes/blob/main/doc/config.md#routes).
81
+ > A "root component" is any lwc in an app route's [content template, layout template](https://github.com/salesforce-experience-platform-emu/lwr-recipes/tree/main/packages/templating#templates), or [`rootComponent` configuration](https://github.com/salesforce-experience-platform-emu/lwr-recipes/blob/main/doc/config.md#routes).
47
82
 
48
- LWR will automatically pass any root component attributes from a [template](https://github.com/salesforce/lwr-recipes/tree/main/packages/templating#templates) as [public properties](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/reactivity_public) during SSR. For example, `my/root` will receive `{ limit: '10' }`.
83
+ LWR will automatically pass any root component attributes from a [template](https://github.com/salesforce-experience-platform-emu/lwr-recipes/tree/main/packages/templating#templates) as [public properties](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/reactivity_public) during SSR. For example, `my/root` will receive `{ limit: '10' }`.
49
84
 
50
85
  ```html
51
86
  <!-- my-app/src/content/page.html -->
@@ -93,6 +128,9 @@ interface PageDataResponse {
93
128
  fetchpriority?: 'high' | 'low' | 'auto';
94
129
  }[];
95
130
  };
131
+ cache?: {
132
+ ttl?: string | number; // time-to-live: time string or number of seconds
133
+ };
96
134
  }
97
135
 
98
136
  type Json = undefined | null | boolean | number | string | Json[] | { [prop: string]: Json };
@@ -102,13 +140,13 @@ In LWR, the SSR process runs in a sandbox. This sandbox supports `globalThis.fet
102
140
 
103
141
  #### JSON
104
142
 
105
- Data in `PageDataResponse.props` is serialized into the page document as JSON. LWR passes this data to the component as [public properties](<(https://developer.salesforce.com/docs/component-library/documentation/en/lwc/reactivity_public)>) during both SSR and [client hydration](#client-hydration).
143
+ Data in `PageDataResponse.props` is serialized into the page document as JSON for each root component. LWR passes this data to the root component as [public properties](<(https://developer.salesforce.com/docs/component-library/documentation/en/lwc/reactivity_public)>) during both SSR and [client hydration](#client-hydration). In order to receive the properties, the root component must declare them using `@api` (see the `data` property in the [example](#data-example) below).
106
144
 
107
145
  #### Markup
108
146
 
109
147
  Data in `PageDataResponse.markup` is serialized into the page document as HTML. LWR adds each `markup.link` returned by `getPageData()` to the `<head>` section of the page document. For example, developers can [preload images](https://developer.chrome.com/blog/link-rel-preload/) to improve a page's performance on the client.
110
148
 
111
- #### Example
149
+ #### Data example
112
150
 
113
151
  The `getPageData()` hook is exported as a function from a root component module:
114
152
 
@@ -147,6 +185,7 @@ export async function getPageData(context: SsrRequestContext): Promise<PageDataR
147
185
  },
148
186
  ],
149
187
  },
188
+ cache: { ttl: '60s' },
150
189
  };
151
190
  }
152
191
  ```
@@ -157,22 +196,107 @@ Notes:
157
196
  - The author of `getPageData()` is responsible for validating the `params` and `query` from `SsrRequestContext` before using them.
158
197
  - The **same** `props` returned by `getPageData()` are passed to the component during server rendering **and** client hydration.
159
198
 
199
+ ### Caching
200
+
201
+ The `getPageData()` SSR hook can return a time-to-live (TTL) for the page in `PageDataReponse.cache.ttl`. The `ttl` value can be a string (see examples [here](https://www.npmjs.com/package/ms)) or a number of seconds. It will be used to set the `max-age` of the `Cache-control` header on the page document. If the root components on a page return more than one TTL, then the shortest one wins.
202
+
203
+ There are two other ways to set a TTL on a page document:
204
+
205
+ 1. in a `route` in the LWR app config (see doc [here](https://github.com/salesforce-experience-platform-emu/lwr-recipes/blob/main/doc/config.md#routes))
206
+ 2. from the return value of a `RouteHandlerFunction` (see doc [here](https://github.com/salesforce-experience-platform-emu/lwr-recipes/tree/main/packages/templating#route-handler-params))
207
+
208
+ LWR will use the shortest TTL value from **all** sources to set the `max-age` of the `Cache-control` header on the page.
209
+
160
210
  ### Client hydration
161
211
 
162
212
  When SSRed component HTML reaches the browser, each root component is automatically hydrated. LWR uses the [LWC `hydrateComponent()` API](https://rfcs.lwc.dev/rfcs/lwc/0117-ssr-rehydration) to do so. Hydrating a component starts its component lifecycle and makes it interactive.
163
213
 
164
- ### Debugging
214
+ ## Best Practices
215
+
216
+ ### SSR execution
217
+
218
+ When SSR runs, the LWC framework executes the following functions for a given component:
219
+
220
+ - `constructor`
221
+ - `connectedCallback`
222
+ - getters
223
+ - setters
224
+ - any other functions called by the above
225
+
226
+ So in order to SSR a component successfully, these functions must be both [portable](#portability) and [synchronous](#synchronous-code).
227
+
228
+ ### Portability
229
+
230
+ To SSR successfully, a component must be **portable**. Code is portable when it can run in a headless environment (ie: no DOM APIs, see LWC SSR constraints [here](https://rfcs.lwc.dev/rfcs/lwc/0112-server-engine#constraints)). SSRable components are also called **isomorphic**.
231
+
232
+ Examples of non-portable code and objects include:
233
+
234
+ - `window`
235
+ - `document`
236
+ - selector functions (`template.querySelector`, `template.querySelectorAll`, ...)
237
+ - `classList`
238
+ - JavaScript eventing
239
+
240
+ Isomorphic components can:
241
+
242
+ - place non-portable code in a function which is not [executed during SSR](#ssr-execution), such as the `renderedCallback`.
243
+ - guard non-portable objects so they are not accessed during SSR:
244
+
245
+ ```js
246
+ // guard usage of the window object so it does not throw during SSR
247
+ export default class App extends LightningElement {
248
+ connectedCallback() {
249
+ if (typeof window !== 'undefined') {
250
+ window.addEventListener('error', (evt) => {
251
+ console.error(`⚠️ Uncaught error: ${evt.message}`);
252
+ });
253
+ }
254
+ }
255
+ }
256
+ ```
257
+
258
+ ### Synchronous code
259
+
260
+ The LWC SSR process run in a single synchronous pass. So any asynchronous code **will not be SSRed**. Asynchronous code includes:
261
+
262
+ - [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
263
+ - [`async`/`await`](https://www.w3schools.com/js/js_async.asp)
264
+ - [dynamic imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import)
265
+
266
+ ## Debugging
267
+
268
+ ### Debug logging
269
+
270
+ See fine-grained logging with the `LOG_LEVEL` flag:
271
+
272
+ ```sh
273
+ LOG_LEVEL=debug MODE=prod-compat yarn start # start up the LWR server, in any mode
274
+ ```
275
+
276
+ ### Breakpoints
165
277
 
166
278
  With the introduction of [@locker/near-membrane-node](https://github.com/salesforce/near-membrane/tree/main/packages/near-membrane-node) sandbox, debugging a module in server side rendering is straightforward:
167
279
 
168
- 1. setting a `debugger;` statement in your module
169
- 2. attach node process to the LWR server
280
+ 1. Set a `debugger;` statement in a module.
281
+ 2. Attach node process to the LWR server.
170
282
 
171
283
  ```sh
172
- LOCKER_SB=true yarn lwr:example:debug
284
+ node --inspect-brk scripts/start.js # where start.js creates and starts the LWR server
173
285
  ```
174
286
 
175
- 3. visit intended url(http://localhost:3000/ssr in this example) with a browser
176
- 4. when `debugger;` statement is executed, VS Code would break with the VM tab showing up
287
+ 3. When the `debugger;` statement is executed, VS Code will break using the VM tab.
177
288
 
178
289
  ![Sandbox Debugging Screenshot](../../../assets/sandbox-debug.png)
290
+
291
+ > Debugging the LWR sample application (ie: `lwr-example-app`) using `yarn lwr:example:debug`.
292
+
293
+ ### Inspect bundles
294
+
295
+ When SSR fails due to a [portability](#portability), it can be difficult to figure out which component is causing the issue. The broken component can be found by inspecting the module bundle for the page:
296
+
297
+ 1. Go to the LWR config for the app (ie: _lwr.config.json_), and turn the [`ssr` flag](#turn-on-ssr) on the broken route **off**.
298
+ 2. Start the LWR app and load the route in the browser.
299
+ 3. Inspect the page's module bundles for the problematic code. Module bundles are served from _/1/bundle/..._.
300
+ 4. When the problematic code is found, scroll up to find the component to which it belongs.
301
+
302
+ For example, if SSR fails with this error:`ReferenceError: window is not defined`, then turn off SSR and search the module bundles for prohibited usages of the `window` object.
@@ -35,20 +35,26 @@ import { renderComponent } from '@lwc/engine-server';
35
35
  import Ctor, * as rootComponent from '${rootSpecifier}';
36
36
 
37
37
  (async () => {
38
- // 1. setup page data
39
- const context = globalThis.getContext();
40
- let props = context.props;
41
- let markup;
42
- if (rootComponent.getPageData) {
43
- const data = await rootComponent.getPageData(context);
44
- props = data.props; // overwrite public props
45
- markup = data.markup;
46
- }
38
+ let result, props, markup;
39
+ try {
40
+ // 1. setup page data
41
+ const context = globalThis.getContext();
42
+ props = context.props;
43
+ if (rootComponent.getPageData) {
44
+ const data = await rootComponent.getPageData(context);
45
+ props = data.props; // overwrite public props
46
+ markup = data.markup;
47
+ }
48
+
49
+ // 2. render component
50
+ result = renderComponent('${(0, import_shared_utils.moduleSpecifierToKebabCase)(rootSpecifier)}', Ctor, props || {});
47
51
 
48
- // 2. render component
49
- const result = renderComponent('${(0, import_shared_utils.moduleSpecifierToKebabCase)(rootSpecifier)}', Ctor, props || {});
52
+ } catch(e) {
53
+ // (3) relay error
54
+ globalThis.resolver({ error: e.message || e });
55
+ }
50
56
 
51
- // 3. relay results
57
+ // 3. relay successful results
52
58
  globalThis.resolver({ result, props, markup });
53
59
  })()`;
54
60
  }
@@ -24,10 +24,12 @@ var __toModule = (module2) => {
24
24
  // packages/@lwrjs/lwc-ssr/src/viewTransformer/amd-utils.ts
25
25
  __markAsModule(exports);
26
26
  __export(exports, {
27
- default: () => getCode
27
+ getBundle: () => getBundle,
28
+ getCode: () => getCode
28
29
  });
29
- var import_loader = __toModule(require("@lwrjs/loader"));
30
30
  var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
31
+ var import_identity = __toModule(require("../identity.cjs"));
32
+ var lwcEngineSpecifier = "@lwc/engine-server";
31
33
  async function readableToString(readable) {
32
34
  let result = "";
33
35
  for await (const chunk of readable) {
@@ -36,20 +38,16 @@ async function readableToString(readable) {
36
38
  return result;
37
39
  }
38
40
  var loaderShimSource;
39
- async function getLoaderShim(runtimeEnvironment) {
41
+ async function getLoaderShim(resourceRegistry, runtimeEnvironment) {
40
42
  if (loaderShimSource !== void 0) {
41
43
  return loaderShimSource;
42
44
  }
43
- const amdloaderShimService = new import_loader.default({}, {
44
- runtimeEnvironment,
45
- resourceRegistry: {resolveResourceUri: () => "NOT IMPLEMENTED"}
46
- });
47
- const specifier = (0, import_shared_utils.getFeatureFlags)().LEGACY_LOADER ? "lwr-loader-shim-legacy.bundle.js" : "lwr-loader-shim.bundle.js";
48
- const resource = await amdloaderShimService.getResource({specifier}, runtimeEnvironment);
45
+ const specifier = (0, import_shared_utils.getFeatureFlags)().LEGACY_LOADER ? "lwr-loader-shim-legacy.bundle.min.js" : "lwr-loader-shim.bundle.min.js";
46
+ const resource = await resourceRegistry.getResource({specifier}, runtimeEnvironment);
49
47
  if (resource) {
50
48
  const {stream} = resource;
51
49
  if (stream) {
52
- loaderShimSource = await readableToString(stream);
50
+ loaderShimSource = await readableToString(stream());
53
51
  return loaderShimSource;
54
52
  }
55
53
  }
@@ -73,18 +71,18 @@ function getLwrConfig(bundleSpecifier, lwrVersion) {
73
71
  });
74
72
  }
75
73
  function lwcDefineOverride(lwcSpecifier) {
76
- return `LWR.define("${lwcSpecifier}", ["${lwcSpecifier.replace("lwc", "@lwc/engine-server")}"], function(lwcEngine) { return lwcEngine; });`;
74
+ return `LWR.define("${lwcSpecifier}", ["${lwcSpecifier.replace("lwc", lwcEngineSpecifier)}"], function(lwcEngine) { return lwcEngine; });`;
77
75
  }
78
76
  var GLOBALTHIS_LWR = `globalThis.LWR = globalThis.LWR || {};`;
79
- async function getCode(runtimeEnvironment, lwrVersion, bundleSpecifier, includedModules) {
80
- const loaderShimSource2 = await getLoaderShim(runtimeEnvironment);
77
+ async function getCode(runtimeEnvironment, lwrVersion, bundleSpecifier, includedModules, resourceRegistry) {
78
+ const loaderShimSource2 = await getLoaderShim(resourceRegistry, runtimeEnvironment);
81
79
  const lwrConfigString = JSON.stringify(getLwrConfig(bundleSpecifier, lwrVersion));
82
80
  const lwcSpecifier = includedModules.reduce((specifier, includedModule) => {
83
81
  if (includedModule.startsWith("lwc/v")) {
84
82
  return includedModule;
85
83
  }
86
- if (!specifier && includedModule.startsWith("@lwc/engine-server/v")) {
87
- return includedModule.replace("@lwc/engine-server", "lwc");
84
+ if (!specifier && includedModule.startsWith(`${lwcEngineSpecifier}/v`)) {
85
+ return includedModule.replace(lwcEngineSpecifier, "lwc");
88
86
  }
89
87
  return specifier;
90
88
  }, "");
@@ -95,3 +93,42 @@ async function getCode(runtimeEnvironment, lwrVersion, bundleSpecifier, included
95
93
  lwcSpecifier ? lwcDefineOverride(lwcSpecifier) : ""
96
94
  ];
97
95
  }
96
+ async function getBundle(specifier, moduleBundler, runtimeEnvironment) {
97
+ if ((0, import_shared_utils.getFeatureFlags)().SSR_STATIC_BUNDLES) {
98
+ return buildBundle(specifier, moduleBundler, runtimeEnvironment);
99
+ }
100
+ return bundle(specifier, moduleBundler, runtimeEnvironment, {appendExcludes: false, exclude: ["lwc"]});
101
+ }
102
+ async function bundle(specifier, moduleBundler, runtimeEnvironment, bundleConfigOverrides) {
103
+ return await moduleBundler.getModuleBundle({specifier}, runtimeEnvironment, void 0, bundleConfigOverrides);
104
+ }
105
+ async function buildBundle(ssrSpecifier, moduleBundler, runtimeEnvironment) {
106
+ const rootSpecifier = ssrSpecifier.replace(import_identity.LWC_SSR_PREFIX, "");
107
+ const {code: _rootCode, bundleRecord: rootBundleRecord} = await bundle(rootSpecifier, moduleBundler, runtimeEnvironment);
108
+ let rootCode = _rootCode;
109
+ if (rootBundleRecord.imports) {
110
+ for (const {specifier} of rootBundleRecord.imports) {
111
+ if (specifier !== "lwc") {
112
+ const {code: code2} = await bundle(specifier, moduleBundler, runtimeEnvironment);
113
+ rootCode = code2 + rootCode;
114
+ }
115
+ }
116
+ }
117
+ const {code: lwcEngineCode, version: lwcVersion} = await bundle(lwcEngineSpecifier, moduleBundler, runtimeEnvironment);
118
+ const {
119
+ bundleRecord,
120
+ code: ssrCode,
121
+ version: lwrVersion
122
+ } = await bundle(ssrSpecifier, moduleBundler, runtimeEnvironment, {
123
+ appendExcludes: true,
124
+ exclude: [lwcEngineSpecifier, rootSpecifier]
125
+ });
126
+ const code = rootCode + lwcEngineCode + ssrCode;
127
+ bundleRecord.includedModules.push(`${lwcEngineSpecifier}/v/${(0, import_shared_utils.normalizeVersionToUri)(lwcVersion)}`);
128
+ return {
129
+ bundleRecord,
130
+ code,
131
+ specifier: ssrSpecifier,
132
+ version: lwrVersion
133
+ };
134
+ }
@@ -29,63 +29,69 @@ __export(exports, {
29
29
  var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
30
30
  var import_identity = __toModule(require("../identity.cjs"));
31
31
  var import_ssr_element = __toModule(require("./ssr-element.cjs"));
32
- function lwcSsrViewTranformer(options, {moduleBundler}) {
32
+ function lwcSsrViewTranformer(options, {moduleBundler, resourceRegistry}) {
33
33
  return {
34
34
  name: "ssr-lwc-transformer",
35
35
  async link(stringBuilder, viewContext, {customElements}) {
36
- import_shared_utils.logger.debug("lwcSsrViewTranformer");
37
- import_shared_utils.logger.verbose("lwcSsrViewTranformer input", stringBuilder);
38
- if (process.env.LOCKER === "true") {
39
- return;
36
+ import_shared_utils.logger.debug("[lwcSsrViewTranformer] link");
37
+ import_shared_utils.logger.verbose("[lwcSsrViewTranformer] link input", stringBuilder);
38
+ if (process.env.LOCKER === "true" || !viewContext.view.bootstrap?.ssr) {
39
+ return {};
40
40
  }
41
- if (viewContext.view.bootstrap?.ssr) {
42
- const ssrModules = [];
43
- for (const {tagName, location, props} of customElements) {
44
- if (location) {
45
- const {startOffset, endOffset} = location;
46
- const moduleSpecifier = (0, import_shared_utils.kebabCaseToModuleSpecifer)(tagName);
47
- ssrModules.push({
48
- startOffset,
49
- endOffset,
50
- props,
51
- tagName,
52
- specifier: `${import_identity.LWC_SSR_PREFIX}${moduleSpecifier}`
53
- });
54
- }
55
- }
56
- const ssrProps = {};
57
- let ssrLinks = "";
58
- await Promise.all(ssrModules.map(({specifier, tagName, props, startOffset, endOffset}) => {
59
- return (0, import_ssr_element.ssrElement)({specifier, props}, moduleBundler, viewContext).then(({html, props: props2, markup: {links = []} = {links: []}}) => {
60
- if (props2) {
61
- const propsId = (0, import_identity.getPropsId)();
62
- ssrProps[propsId] = props2;
63
- const [, remain] = html.split(`<${tagName}`);
64
- html = [`<${tagName}`, ` ${import_identity.SSR_PROPS_ATTR}="${propsId}"`, remain].join("");
65
- }
66
- links.forEach(({href, rel, as, fetchpriority}) => {
67
- const relStr = rel ? ` rel="${rel}"` : "", asStr = as ? ` as="${as}"` : "", fetchStr = fetchpriority ? ` fetchpriority="${fetchpriority}"` : "";
68
- ssrLinks += `<link href="${href}"${relStr}${asStr}${fetchStr}>
69
- `;
70
- });
71
- stringBuilder.overwrite(startOffset, endOffset, html);
72
- }).catch((err) => {
73
- import_shared_utils.logger.warn(`Server-side rendering for "${specifier}" failed. Falling back to client-side rendering. Reason: `, err.stack);
41
+ const ssrModules = [];
42
+ for (const {tagName, location, props} of customElements) {
43
+ if (location) {
44
+ const {startOffset, endOffset} = location;
45
+ const moduleSpecifier = (0, import_shared_utils.kebabCaseToModuleSpecifier)(tagName);
46
+ ssrModules.push({
47
+ startOffset,
48
+ endOffset,
49
+ props,
50
+ tagName,
51
+ specifier: `${import_identity.LWC_SSR_PREFIX}${moduleSpecifier}`
74
52
  });
75
- }));
76
- if (Object.keys(ssrProps).length) {
77
- stringBuilder.prependLeft(ssrModules[0].startOffset, `<script type="application/javascript">globalThis.LWR = globalThis.LWR || {};globalThis.LWR.${import_identity.SSR_PROPS_KEY} = ${JSON.stringify(ssrProps)};</script>`);
78
53
  }
79
- if (ssrLinks) {
80
- const headIndex = stringBuilder.original.indexOf("</head>");
81
- if (headIndex >= 0) {
82
- stringBuilder.prependLeft(headIndex, ssrLinks);
83
- } else {
84
- import_shared_utils.logger.error("Adding links during server-side rendering failed. Could not find the </head> tag.");
54
+ }
55
+ const ssrProps = {};
56
+ let ssrLinks = "";
57
+ let pageTtl;
58
+ await Promise.all(ssrModules.map(({specifier, tagName, props, startOffset, endOffset}) => {
59
+ return (0, import_ssr_element.ssrElement)({specifier, props}, moduleBundler, resourceRegistry, viewContext).then(({
60
+ html,
61
+ props: props2,
62
+ markup: {links = []} = {links: []},
63
+ cache: {ttl} = {}
64
+ }) => {
65
+ pageTtl = (0, import_shared_utils.shortestTtl)(ttl, pageTtl);
66
+ if (props2) {
67
+ const propsId = (0, import_identity.getPropsId)();
68
+ ssrProps[propsId] = props2;
69
+ const [, remain] = html.split(`<${tagName}`);
70
+ html = [`<${tagName}`, ` ${import_identity.SSR_PROPS_ATTR}="${propsId}"`, remain].join("");
85
71
  }
72
+ links.forEach(({href, rel, as, fetchpriority}) => {
73
+ const relStr = rel ? ` rel="${rel}"` : "", asStr = as ? ` as="${as}"` : "", fetchStr = fetchpriority ? ` fetchpriority="${fetchpriority}"` : "";
74
+ ssrLinks += `<link href="${href}"${relStr}${asStr}${fetchStr}>
75
+ `;
76
+ });
77
+ stringBuilder.overwrite(startOffset, endOffset, html);
78
+ }).catch((err) => {
79
+ import_shared_utils.logger.warn(`Server-side rendering for "${specifier}" failed. Reason: `, err.stack);
80
+ });
81
+ }));
82
+ if (Object.keys(ssrProps).length) {
83
+ stringBuilder.prependLeft(ssrModules[0].startOffset, `<script type="application/javascript">globalThis.LWR = globalThis.LWR || {};globalThis.LWR.${import_identity.SSR_PROPS_KEY} = ${JSON.stringify(ssrProps)};</script>`);
84
+ }
85
+ if (ssrLinks) {
86
+ const headIndex = stringBuilder.original.indexOf("</head>");
87
+ if (headIndex >= 0) {
88
+ stringBuilder.prependLeft(headIndex, ssrLinks);
89
+ } else {
90
+ import_shared_utils.logger.error("Adding links during server-side rendering failed. Could not find the </head> tag.");
86
91
  }
87
- import_shared_utils.logger.verbose("lwcSsrViewTranformer response", stringBuilder);
88
92
  }
93
+ import_shared_utils.logger.verbose("lwcSsrViewTranformer response", stringBuilder);
94
+ return {cache: {ttl: pageTtl}};
89
95
  }
90
96
  };
91
97
  }
@@ -28,9 +28,10 @@ __export(exports, {
28
28
  });
29
29
  var import_sandbox_worker = __toModule(require("./sandbox-worker.cjs"));
30
30
  var import_sandbox_locker = __toModule(require("./sandbox-locker.cjs"));
31
+ var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
31
32
  function runCode(codes, context) {
32
- if (process.env.LOCKER_SB === "true") {
33
- return (0, import_sandbox_locker.default)(codes, context);
33
+ if ((0, import_shared_utils.getFeatureFlags)().SSR_SANDBOX_WORKER) {
34
+ return (0, import_sandbox_worker.default)(codes, context);
34
35
  }
35
- return (0, import_sandbox_worker.default)(codes, context);
36
+ return (0, import_sandbox_locker.default)(codes, context);
36
37
  }
@@ -29,7 +29,8 @@ __export(exports, {
29
29
  var import_amd_utils = __toModule(require("./amd-utils.cjs"));
30
30
  var import_sandbox = __toModule(require("./sandbox.cjs"));
31
31
  var import_perf_hooks = __toModule(require("perf_hooks"));
32
- async function ssrElement({specifier, props: templateProps}, moduleBundler, {runtimeEnvironment, runtimeParams}) {
32
+ var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
33
+ async function ssrElement({specifier, props: templateProps}, moduleBundler, resourceRegistry, {runtimeEnvironment, runtimeParams}) {
33
34
  const {format} = runtimeEnvironment;
34
35
  const {
35
36
  bundleRecord,
@@ -41,9 +42,7 @@ async function ssrElement({specifier, props: templateProps}, moduleBundler, {run
41
42
  alias: {
42
43
  lwc: "@lwc/engine-server"
43
44
  }
44
- }) : await moduleBundler.getModuleBundle({specifier}, runtimeEnvironment, void 0, {
45
- exclude: ["lwc"]
46
- });
45
+ }) : await (0, import_amd_utils.getBundle)(specifier, moduleBundler, runtimeEnvironment);
47
46
  const context = {
48
47
  props: templateProps,
49
48
  params: runtimeParams.params || {},
@@ -51,12 +50,14 @@ async function ssrElement({specifier, props: templateProps}, moduleBundler, {run
51
50
  locale: runtimeParams.locale || runtimeEnvironment.defaultLocale
52
51
  };
53
52
  const startTime = import_perf_hooks.performance.now();
54
- const {result, props, markup} = format === "amd" ? await (0, import_sandbox.default)([
55
- ...await (0, import_amd_utils.default)(runtimeEnvironment, version.replace(/\./g, "_"), bundleSpecifier, bundleRecord.includedModules),
53
+ const {result, props, markup, cache, error} = format === "amd" ? await (0, import_sandbox.default)([
54
+ ...await (0, import_amd_utils.getCode)(runtimeEnvironment, version.replace(/\./g, "_"), bundleSpecifier, bundleRecord.includedModules, resourceRegistry),
56
55
  code
57
56
  ], context) : await (0, import_sandbox.default)([code], context);
58
57
  const endTime = import_perf_hooks.performance.now();
59
58
  const timeDiff = endTime - startTime;
60
- console.log(`[${specifier} SSR] complete in ${timeDiff} ms`);
61
- return {html: result, props, markup};
59
+ import_shared_utils.logger.info(`[Server-side Rendering] ${specifier} in ${Math.round(timeDiff)} ms`);
60
+ if (error)
61
+ throw new Error(error);
62
+ return {html: result, props, markup, cache};
62
63
  }
@@ -7,7 +7,7 @@ import { ModuleCompiled, ModuleEntry, ModuleProvider, ProviderContext, AbstractM
7
7
  /**
8
8
  * Create the virtual source for a module which server-side renders the given component.
9
9
  * This code is meant to be executed in a worker on the server; it is run from "lwc-ssr/viewTransformer#ssr-element" during linking.
10
- * The result is posted to the parentPort and the Promise in the main thread resolves.
10
+ * The result is returned to the caller and the Promise in the main thread resolves.
11
11
  * If available, getPageData() is called on the root component to mutate context.props and gather page markup
12
12
  * @param rootSpecifier - The specifier for the component to SSR
13
13
  * @returns the generated module source
@@ -8,7 +8,7 @@ import { LWC_SSR_PREFIX } from '../identity.js';
8
8
  /**
9
9
  * Create the virtual source for a module which server-side renders the given component.
10
10
  * This code is meant to be executed in a worker on the server; it is run from "lwc-ssr/viewTransformer#ssr-element" during linking.
11
- * The result is posted to the parentPort and the Promise in the main thread resolves.
11
+ * The result is returned to the caller and the Promise in the main thread resolves.
12
12
  * If available, getPageData() is called on the root component to mutate context.props and gather page markup
13
13
  * @param rootSpecifier - The specifier for the component to SSR
14
14
  * @returns the generated module source
@@ -19,20 +19,26 @@ import { renderComponent } from '@lwc/engine-server';
19
19
  import Ctor, * as rootComponent from '${rootSpecifier}';
20
20
 
21
21
  (async () => {
22
- // 1. setup page data
23
- const context = globalThis.getContext();
24
- let props = context.props;
25
- let markup;
26
- if (rootComponent.getPageData) {
27
- const data = await rootComponent.getPageData(context);
28
- props = data.props; // overwrite public props
29
- markup = data.markup;
30
- }
22
+ let result, props, markup;
23
+ try {
24
+ // 1. setup page data
25
+ const context = globalThis.getContext();
26
+ props = context.props;
27
+ if (rootComponent.getPageData) {
28
+ const data = await rootComponent.getPageData(context);
29
+ props = data.props; // overwrite public props
30
+ markup = data.markup;
31
+ }
31
32
 
32
- // 2. render component
33
- const result = renderComponent('${moduleSpecifierToKebabCase(rootSpecifier)}', Ctor, props || {});
33
+ // 2. render component
34
+ result = renderComponent('${moduleSpecifierToKebabCase(rootSpecifier)}', Ctor, props || {});
35
+
36
+ } catch(e) {
37
+ // (3) relay error
38
+ globalThis.resolver({ error: e.message || e });
39
+ }
34
40
 
35
- // 3. relay results
41
+ // 3. relay successful results
36
42
  globalThis.resolver({ result, props, markup });
37
43
  })()`;
38
44
  }
@@ -1,3 +1,7 @@
1
- import { RuntimeEnvironment } from '@lwrjs/types';
2
- export default function getCode(runtimeEnvironment: RuntimeEnvironment, lwrVersion: string, bundleSpecifier: string, includedModules: string[]): Promise<string[]>;
1
+ import type { BundleDefinition, PublicModuleBundler, PublicResourceRegistry, RuntimeEnvironment } from '@lwrjs/types';
2
+ export declare function getCode(runtimeEnvironment: RuntimeEnvironment, lwrVersion: string, bundleSpecifier: string, includedModules: string[], resourceRegistry: PublicResourceRegistry): Promise<string[]>;
3
+ declare type PartialBundleDefinition = Pick<BundleDefinition, 'bundleRecord' | 'code' | 'specifier' | 'version'>;
4
+ export declare function getBundle(specifier: string, // e.g. "@lwrjs/lwc-ssr/root/component"
5
+ moduleBundler: PublicModuleBundler, runtimeEnvironment: RuntimeEnvironment): Promise<PartialBundleDefinition>;
6
+ export {};
3
7
  //# sourceMappingURL=amd-utils.d.ts.map
@@ -1,5 +1,6 @@
1
- import AmdLoaderShimService from '@lwrjs/loader';
2
- import { getFeatureFlags } from '@lwrjs/shared-utils';
1
+ import { getFeatureFlags, normalizeVersionToUri } from '@lwrjs/shared-utils';
2
+ import { LWC_SSR_PREFIX } from '../identity.js';
3
+ const lwcEngineSpecifier = '@lwc/engine-server';
3
4
  async function readableToString(readable) {
4
5
  let result = '';
5
6
  for await (const chunk of readable) {
@@ -8,22 +9,18 @@ async function readableToString(readable) {
8
9
  return result;
9
10
  }
10
11
  let loaderShimSource;
11
- async function getLoaderShim(runtimeEnvironment) {
12
+ async function getLoaderShim(resourceRegistry, runtimeEnvironment) {
12
13
  if (loaderShimSource !== undefined) {
13
14
  return loaderShimSource;
14
15
  }
15
- const amdloaderShimService = new AmdLoaderShimService({}, {
16
- runtimeEnvironment,
17
- resourceRegistry: { resolveResourceUri: () => 'NOT IMPLEMENTED' },
18
- });
19
16
  const specifier = getFeatureFlags().LEGACY_LOADER
20
- ? 'lwr-loader-shim-legacy.bundle.js'
21
- : 'lwr-loader-shim.bundle.js';
22
- const resource = await amdloaderShimService.getResource({ specifier }, runtimeEnvironment);
17
+ ? 'lwr-loader-shim-legacy.bundle.min.js'
18
+ : 'lwr-loader-shim.bundle.min.js';
19
+ const resource = await resourceRegistry.getResource({ specifier }, runtimeEnvironment);
23
20
  if (resource) {
24
21
  const { stream } = resource;
25
22
  if (stream) {
26
- loaderShimSource = await readableToString(stream);
23
+ loaderShimSource = await readableToString(stream());
27
24
  return loaderShimSource;
28
25
  }
29
26
  }
@@ -51,11 +48,11 @@ function getLwrConfig(bundleSpecifier, lwrVersion) {
51
48
  }
52
49
  // lwc/v/2_13_0 -> @lwc/engine-server/v/2_13_0
53
50
  function lwcDefineOverride(lwcSpecifier) {
54
- return `LWR.define("${lwcSpecifier}", ["${lwcSpecifier.replace('lwc', '@lwc/engine-server')}"], function(lwcEngine) { return lwcEngine; });`;
51
+ return `LWR.define("${lwcSpecifier}", ["${lwcSpecifier.replace('lwc', lwcEngineSpecifier)}"], function(lwcEngine) { return lwcEngine; });`;
55
52
  }
56
53
  const GLOBALTHIS_LWR = `globalThis.LWR = globalThis.LWR || {};`;
57
- export default async function getCode(runtimeEnvironment, lwrVersion, bundleSpecifier, includedModules) {
58
- const loaderShimSource = await getLoaderShim(runtimeEnvironment);
54
+ export async function getCode(runtimeEnvironment, lwrVersion, bundleSpecifier, includedModules, resourceRegistry) {
55
+ const loaderShimSource = await getLoaderShim(resourceRegistry, runtimeEnvironment);
59
56
  const lwrConfigString = JSON.stringify(getLwrConfig(bundleSpecifier, lwrVersion));
60
57
  const lwcSpecifier = includedModules.reduce((specifier, includedModule) => {
61
58
  if (includedModule.startsWith('lwc/v')) {
@@ -63,8 +60,8 @@ export default async function getCode(runtimeEnvironment, lwrVersion, bundleSpec
63
60
  }
64
61
  // define `lwc` when `@lwc/engine-server` is included in the bundle
65
62
  // `lwc` will be excluded by default when bundling is enabled
66
- if (!specifier && includedModule.startsWith('@lwc/engine-server/v')) {
67
- return includedModule.replace('@lwc/engine-server', 'lwc');
63
+ if (!specifier && includedModule.startsWith(`${lwcEngineSpecifier}/v`)) {
64
+ return includedModule.replace(lwcEngineSpecifier, 'lwc');
68
65
  }
69
66
  return specifier;
70
67
  }, '');
@@ -78,4 +75,55 @@ export default async function getCode(runtimeEnvironment, lwrVersion, bundleSpec
78
75
  lwcSpecifier ? lwcDefineOverride(lwcSpecifier) : '',
79
76
  ];
80
77
  }
78
+ export async function getBundle(specifier, // e.g. "@lwrjs/lwc-ssr/root/component"
79
+ moduleBundler, runtimeEnvironment) {
80
+ if (getFeatureFlags().SSR_STATIC_BUNDLES) {
81
+ // concatenate existing bundles SSGed at build time
82
+ return buildBundle(specifier, moduleBundler, runtimeEnvironment);
83
+ }
84
+ // create a new SSR bundle
85
+ // "lwc" is aliased as "@lwc/engine-server" in the AMD worker code
86
+ return bundle(specifier, moduleBundler, runtimeEnvironment, { appendExcludes: false, exclude: ['lwc'] });
87
+ }
88
+ async function bundle(specifier, moduleBundler, runtimeEnvironment, bundleConfigOverrides) {
89
+ return await moduleBundler.getModuleBundle({ specifier }, runtimeEnvironment, undefined, bundleConfigOverrides);
90
+ }
91
+ // Build a SSR bundle for a root component by concatenating:
92
+ // - the root component bundle
93
+ // - @lwc/engine-server
94
+ // - the SSR bundle for the root specifier
95
+ async function buildBundle(ssrSpecifier, moduleBundler, runtimeEnvironment) {
96
+ // 1. Get the bundle for the root component
97
+ const rootSpecifier = ssrSpecifier.replace(LWC_SSR_PREFIX, '');
98
+ const { code: _rootCode, bundleRecord: rootBundleRecord } = await bundle(rootSpecifier, moduleBundler, runtimeEnvironment);
99
+ // 1a) Add the bundles imported into the root component bundle
100
+ let rootCode = _rootCode;
101
+ if (rootBundleRecord.imports) {
102
+ for (const { specifier } of rootBundleRecord.imports) {
103
+ if (specifier !== 'lwc') {
104
+ // TODO: recurse the imports to support chains of excluded modules
105
+ // eslint-disable-next-line no-await-in-loop
106
+ const { code } = await bundle(specifier, moduleBundler, runtimeEnvironment);
107
+ rootCode = code + rootCode;
108
+ }
109
+ }
110
+ }
111
+ // 2. Get the bundle for the LWC engine
112
+ const { code: lwcEngineCode, version: lwcVersion } = await bundle(lwcEngineSpecifier, moduleBundler, runtimeEnvironment);
113
+ // 3. Get the SSR bundle for the root component
114
+ // Exclude the LWC engine and root component from this bundle. Use the pre-built bundles fetched above instead.
115
+ const { bundleRecord, code: ssrCode, version: lwrVersion, } = await bundle(ssrSpecifier, moduleBundler, runtimeEnvironment, {
116
+ appendExcludes: true,
117
+ exclude: [lwcEngineSpecifier, rootSpecifier],
118
+ });
119
+ // Now concatenate the code gathered above to create the SSR bundle for the root component
120
+ const code = rootCode + lwcEngineCode + ssrCode;
121
+ bundleRecord.includedModules.push(`${lwcEngineSpecifier}/v/${normalizeVersionToUri(lwcVersion)}`); // for LWC aliasing
122
+ return {
123
+ bundleRecord,
124
+ code,
125
+ specifier: ssrSpecifier,
126
+ version: lwrVersion,
127
+ };
128
+ }
81
129
  //# sourceMappingURL=amd-utils.js.map
@@ -20,6 +20,6 @@ interface SsrPluginOptions {
20
20
  * 4. The view/page document now contains SSRed components, which will be sent to the client
21
21
  * 5. During bootstrap on the client, the "lwr/initSsr" module will hydrate ALL the custom elements on the page
22
22
  */
23
- export default function lwcSsrViewTranformer(options: SsrPluginOptions, { moduleBundler }: ProviderContext): ViewTransformPlugin;
23
+ export default function lwcSsrViewTranformer(options: SsrPluginOptions, { moduleBundler, resourceRegistry }: ProviderContext): ViewTransformPlugin;
24
24
  export {};
25
25
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,4 @@
1
- import { kebabCaseToModuleSpecifer, logger } from '@lwrjs/shared-utils';
1
+ import { kebabCaseToModuleSpecifier, logger, shortestTtl } from '@lwrjs/shared-utils';
2
2
  import { LWC_SSR_PREFIX, SSR_PROPS_ATTR, SSR_PROPS_KEY, getPropsId } from '../identity.js';
3
3
  import { ssrElement } from './ssr-element.js';
4
4
  /**
@@ -19,76 +19,78 @@ import { ssrElement } from './ssr-element.js';
19
19
  * 4. The view/page document now contains SSRed components, which will be sent to the client
20
20
  * 5. During bootstrap on the client, the "lwr/initSsr" module will hydrate ALL the custom elements on the page
21
21
  */
22
- export default function lwcSsrViewTranformer(options, { moduleBundler }) {
22
+ export default function lwcSsrViewTranformer(options, { moduleBundler, resourceRegistry }) {
23
23
  return {
24
24
  name: 'ssr-lwc-transformer',
25
25
  async link(stringBuilder, viewContext, { customElements }) {
26
- logger.debug('lwcSsrViewTranformer');
27
- logger.verbose('lwcSsrViewTranformer input', stringBuilder);
26
+ logger.debug('[lwcSsrViewTranformer] link');
27
+ logger.verbose('[lwcSsrViewTranformer] link input', stringBuilder);
28
28
  // SSR currently does not support locker because the module constructor returns undefined
29
29
  // and the call to renderComponent would fail
30
- if (process.env.LOCKER === 'true') {
31
- return;
30
+ if (process.env.LOCKER === 'true' || !viewContext.view.bootstrap?.ssr) {
31
+ return {}; // no SSR
32
32
  }
33
- if (viewContext.view.bootstrap?.ssr) {
34
- // Gather all the SSRable custom elements (ie: root components) into 1 list
35
- const ssrModules = [];
36
- for (const { tagName, location, props } of customElements) {
37
- if (location) {
38
- const { startOffset, endOffset } = location;
39
- const moduleSpecifier = kebabCaseToModuleSpecifer(tagName);
40
- ssrModules.push({
41
- startOffset,
42
- endOffset,
43
- props,
44
- tagName,
45
- specifier: `${LWC_SSR_PREFIX}${moduleSpecifier}`,
46
- });
47
- }
48
- }
49
- // SSR and gather the properties and links for each eligible custom element, in parallel
50
- const ssrProps = {};
51
- let ssrLinks = '';
52
- await Promise.all(ssrModules.map(({ specifier, tagName, props, startOffset, endOffset }) => {
53
- return ssrElement({ specifier, props }, moduleBundler, viewContext)
54
- .then(({ html, props, markup: { links = [] } = { links: [] } }) => {
55
- if (props) {
56
- // Add the props id to the HTML for the custom element
57
- // eg: <some-cmp> -> <some-cmp data-lwr-props-id="1234">
58
- const propsId = getPropsId();
59
- ssrProps[propsId] = props;
60
- const [, remain] = html.split(`<${tagName}`);
61
- html = [`<${tagName}`, ` ${SSR_PROPS_ATTR}="${propsId}"`, remain].join('');
62
- }
63
- links.forEach(({ href, rel, as, fetchpriority }) => {
64
- // Create HTML <link> strings for each item in the links array
65
- const relStr = rel ? ` rel="${rel}"` : '', asStr = as ? ` as="${as}"` : '', fetchStr = fetchpriority ? ` fetchpriority="${fetchpriority}"` : '';
66
- ssrLinks += `<link href="${href}"${relStr}${asStr}${fetchStr}>\n`;
67
- });
68
- // Overwrite the custom element with the SSRed component string
69
- stringBuilder.overwrite(startOffset, endOffset, html);
70
- })
71
- .catch((err) => {
72
- logger.warn(`Server-side rendering for "${specifier}" failed. Falling back to client-side rendering. Reason: `, err.stack);
33
+ // Gather all the SSRable custom elements (ie: root components) into 1 list
34
+ const ssrModules = [];
35
+ for (const { tagName, location, props } of customElements) {
36
+ if (location) {
37
+ const { startOffset, endOffset } = location;
38
+ const moduleSpecifier = kebabCaseToModuleSpecifier(tagName);
39
+ ssrModules.push({
40
+ startOffset,
41
+ endOffset,
42
+ props,
43
+ tagName,
44
+ specifier: `${LWC_SSR_PREFIX}${moduleSpecifier}`,
73
45
  });
74
- }));
75
- if (Object.keys(ssrProps).length) {
76
- // Serialize all root component properties into a single script for the page
77
- // Append the script before the custom elements; it MUST appear before the AMD shim to avoid timing issues
78
- stringBuilder.prependLeft(ssrModules[0].startOffset, `<script type="application/javascript">globalThis.LWR = globalThis.LWR || {};globalThis.LWR.${SSR_PROPS_KEY} = ${JSON.stringify(ssrProps)};</script>`);
79
46
  }
80
- if (ssrLinks) {
81
- // Add all the links to the <head> section of the base document
82
- const headIndex = stringBuilder.original.indexOf('</head>');
83
- if (headIndex >= 0) {
84
- stringBuilder.prependLeft(headIndex, ssrLinks);
85
- }
86
- else {
87
- logger.error('Adding links during server-side rendering failed. Could not find the </head> tag.');
47
+ }
48
+ // SSR and gather the properties and links for each eligible custom element, in parallel
49
+ const ssrProps = {};
50
+ let ssrLinks = '';
51
+ let pageTtl;
52
+ await Promise.all(ssrModules.map(({ specifier, tagName, props, startOffset, endOffset }) => {
53
+ return ssrElement({ specifier, props }, moduleBundler, resourceRegistry, viewContext)
54
+ .then(({ html, props, markup: { links = [] } = { links: [] }, cache: { ttl } = {}, }) => {
55
+ // Keep track of the shortest TTL from all getPageData hooks
56
+ pageTtl = shortestTtl(ttl, pageTtl);
57
+ if (props) {
58
+ // Add the props id to the HTML for the custom element
59
+ // eg: <some-cmp> -> <some-cmp data-lwr-props-id="1234">
60
+ const propsId = getPropsId();
61
+ ssrProps[propsId] = props;
62
+ const [, remain] = html.split(`<${tagName}`);
63
+ html = [`<${tagName}`, ` ${SSR_PROPS_ATTR}="${propsId}"`, remain].join('');
88
64
  }
65
+ links.forEach(({ href, rel, as, fetchpriority }) => {
66
+ // Create HTML <link> strings for each item in the links array
67
+ const relStr = rel ? ` rel="${rel}"` : '', asStr = as ? ` as="${as}"` : '', fetchStr = fetchpriority ? ` fetchpriority="${fetchpriority}"` : '';
68
+ ssrLinks += `<link href="${href}"${relStr}${asStr}${fetchStr}>\n`;
69
+ });
70
+ // Overwrite the custom element with the SSRed component string
71
+ stringBuilder.overwrite(startOffset, endOffset, html);
72
+ })
73
+ .catch((err) => {
74
+ logger.warn(`Server-side rendering for "${specifier}" failed. Reason: `, err.stack);
75
+ });
76
+ }));
77
+ if (Object.keys(ssrProps).length) {
78
+ // Serialize all root component properties into a single script for the page
79
+ // Append the script before the custom elements; it MUST appear before the AMD shim to avoid timing issues
80
+ stringBuilder.prependLeft(ssrModules[0].startOffset, `<script type="application/javascript">globalThis.LWR = globalThis.LWR || {};globalThis.LWR.${SSR_PROPS_KEY} = ${JSON.stringify(ssrProps)};</script>`);
81
+ }
82
+ if (ssrLinks) {
83
+ // Add all the links to the <head> section of the base document
84
+ const headIndex = stringBuilder.original.indexOf('</head>');
85
+ if (headIndex >= 0) {
86
+ stringBuilder.prependLeft(headIndex, ssrLinks);
87
+ }
88
+ else {
89
+ logger.error('Adding links during server-side rendering failed. Could not find the </head> tag.');
89
90
  }
90
- logger.verbose('lwcSsrViewTranformer response', stringBuilder);
91
91
  }
92
+ logger.verbose('lwcSsrViewTranformer response', stringBuilder);
93
+ return { cache: { ttl: pageTtl } };
92
94
  },
93
95
  };
94
96
  }
@@ -1,7 +1,4 @@
1
- import { PageDataResponse, SsrRequestContext } from '@lwrjs/types';
2
- interface SandboxResults extends PageDataResponse {
3
- result?: string;
4
- }
1
+ import type { SsrRequestContext } from '@lwrjs/types';
2
+ import type { SandboxResults } from './sandbox.js';
5
3
  export default function runCode(codes: string[], context: SsrRequestContext): Promise<SandboxResults>;
6
- export {};
7
4
  //# sourceMappingURL=sandbox-locker.d.ts.map
@@ -1,12 +1,9 @@
1
- import { PageDataResponse, SsrRequestContext } from '@lwrjs/types';
2
- interface SandboxResults extends PageDataResponse {
3
- result?: string;
4
- }
1
+ import type { SsrRequestContext } from '@lwrjs/types';
2
+ import type { SandboxResults } from './sandbox.js';
5
3
  /**
6
4
  * Run the SSR module code in a worker, and return the results to the main thread.
7
5
  * @param codes - Code strings which SSR a root component
8
6
  * @returns a promise to the SSRed code string, or an error message
9
7
  */
10
8
  export default function runCode(codes: string[], workerData: SsrRequestContext): Promise<SandboxResults>;
11
- export {};
12
9
  //# sourceMappingURL=sandbox-worker.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { PageDataResponse, SsrRequestContext } from '@lwrjs/types';
2
- interface SandboxResults extends PageDataResponse {
2
+ export interface SandboxResults extends PageDataResponse {
3
3
  result?: string;
4
+ error?: string;
4
5
  }
5
6
  export default function runCode(codes: string[], context: SsrRequestContext): Promise<SandboxResults>;
6
- export {};
7
7
  //# sourceMappingURL=sandbox.d.ts.map
@@ -1,9 +1,11 @@
1
1
  import runCodeOnWorker from './sandbox-worker.js';
2
2
  import runCodeOnLocker from './sandbox-locker.js';
3
+ import { getFeatureFlags } from '@lwrjs/shared-utils';
3
4
  export default function runCode(codes, context) {
4
- if (process.env.LOCKER_SB === 'true') {
5
- return runCodeOnLocker(codes, context);
5
+ if (getFeatureFlags().SSR_SANDBOX_WORKER) {
6
+ return runCodeOnWorker(codes, context);
6
7
  }
7
- return runCodeOnWorker(codes, context);
8
+ // Default to use locker because it works rolled up in MRT
9
+ return runCodeOnLocker(codes, context);
8
10
  }
9
11
  //# sourceMappingURL=sandbox.js.map
@@ -1,4 +1,4 @@
1
- import type { Json, ModuleBundler, PageDataResponse, ViewTranformPluginContext } from '@lwrjs/types';
1
+ import type { Json, PageDataResponse, PublicModuleBundler, PublicResourceRegistry, ViewTranformPluginContext } from '@lwrjs/types';
2
2
  interface SsrResults extends PageDataResponse {
3
3
  html: string;
4
4
  }
@@ -13,6 +13,6 @@ interface SsrResults extends PageDataResponse {
13
13
  export declare function ssrElement({ specifier, props: templateProps }: {
14
14
  specifier: string;
15
15
  props: Json;
16
- }, moduleBundler: ModuleBundler, { runtimeEnvironment, runtimeParams }: ViewTranformPluginContext): Promise<SsrResults>;
16
+ }, moduleBundler: PublicModuleBundler, resourceRegistry: PublicResourceRegistry, { runtimeEnvironment, runtimeParams }: ViewTranformPluginContext): Promise<SsrResults>;
17
17
  export {};
18
18
  //# sourceMappingURL=ssr-element.d.ts.map
@@ -1,6 +1,7 @@
1
- import getCode from './amd-utils.js';
1
+ import { getBundle, getCode } from './amd-utils.js';
2
2
  import runCode from './sandbox.js';
3
3
  import { performance } from 'perf_hooks';
4
+ import { logger } from '@lwrjs/shared-utils';
4
5
  /**
5
6
  * Create a bundle for the given SSR module and run the code in a sandbox.
6
7
  * @param moduleInfo - specifier: The ID of the module, generated by "lwc-ssr/moduleProvider", which SSRs a component
@@ -9,7 +10,7 @@ import { performance } from 'perf_hooks';
9
10
  * @param runtimeEnvironment
10
11
  * @returns a promise to the SSRed code string
11
12
  */
12
- export async function ssrElement({ specifier, props: templateProps }, moduleBundler, { runtimeEnvironment, runtimeParams }) {
13
+ export async function ssrElement({ specifier, props: templateProps }, moduleBundler, resourceRegistry, { runtimeEnvironment, runtimeParams }) {
13
14
  const { format } = runtimeEnvironment;
14
15
  const { bundleRecord, code, specifier: bundleSpecifier, version, } = format === 'esm'
15
16
  ? await moduleBundler.getModuleBundle({ specifier },
@@ -23,10 +24,7 @@ export async function ssrElement({ specifier, props: templateProps }, moduleBund
23
24
  lwc: '@lwc/engine-server',
24
25
  },
25
26
  })
26
- : await moduleBundler.getModuleBundle({ specifier }, runtimeEnvironment, undefined, {
27
- // "lwc" will be defined as an alias to "@lwc/engine-server" in the AMD worker code.
28
- exclude: ['lwc'],
29
- });
27
+ : await getBundle(specifier, moduleBundler, runtimeEnvironment);
30
28
  // Gather context to send into the SSR sandbox
31
29
  const context = {
32
30
  props: templateProps,
@@ -36,15 +34,17 @@ export async function ssrElement({ specifier, props: templateProps }, moduleBund
36
34
  };
37
35
  // Get the SSR string and properties bag
38
36
  const startTime = performance.now();
39
- const { result, props, markup } = format === 'amd'
37
+ const { result, props, markup, cache, error } = format === 'amd'
40
38
  ? await runCode([
41
- ...(await getCode(runtimeEnvironment, version.replace(/\./g, '_'), bundleSpecifier, bundleRecord.includedModules)),
39
+ ...(await getCode(runtimeEnvironment, version.replace(/\./g, '_'), bundleSpecifier, bundleRecord.includedModules, resourceRegistry)),
42
40
  code,
43
41
  ], context)
44
42
  : await runCode([code], context);
45
43
  const endTime = performance.now();
46
44
  const timeDiff = endTime - startTime;
47
- console.log(`[${specifier} SSR] complete in ${timeDiff} ms`);
48
- return { html: result, props, markup };
45
+ logger.info(`[Server-side Rendering] ${specifier} in ${Math.round(timeDiff)} ms`);
46
+ if (error)
47
+ throw new Error(error);
48
+ return { html: result, props, markup, cache };
49
49
  }
50
50
  //# sourceMappingURL=ssr-element.js.map
package/package.json CHANGED
@@ -4,15 +4,15 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.9.0-alpha.3",
7
+ "version": "0.9.0-alpha.30",
8
8
  "homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "https://github.com/salesforce/lwr.git",
11
+ "url": "https://github.com/salesforce-experience-platform-emu/lwr.git",
12
12
  "directory": "packages/@lwrjs/lwc-ssr"
13
13
  },
14
14
  "bugs": {
15
- "url": "https://github.com/salesforce/lwr/issues"
15
+ "url": "https://github.com/salesforce-experience-platform-emu/lwr/issues"
16
16
  },
17
17
  "types": "build/es/index.d.ts",
18
18
  "type": "module",
@@ -33,16 +33,16 @@
33
33
  "build/**/*.d.ts"
34
34
  ],
35
35
  "dependencies": {
36
- "@locker/near-membrane-node": "^0.11.6",
37
- "@lwrjs/diagnostics": "0.9.0-alpha.3",
38
- "@lwrjs/shared-utils": "0.9.0-alpha.3",
39
- "node-fetch": "^2.6.1"
36
+ "@locker/near-membrane-node": "^0.12.0",
37
+ "@lwrjs/diagnostics": "0.9.0-alpha.30",
38
+ "@lwrjs/shared-utils": "0.9.0-alpha.30",
39
+ "node-fetch": "^2.6.8"
40
40
  },
41
41
  "devDependencies": {
42
- "@lwrjs/types": "0.9.0-alpha.3"
42
+ "@lwrjs/types": "0.9.0-alpha.30"
43
43
  },
44
44
  "engines": {
45
- "node": ">=14.15.4 <19"
45
+ "node": ">=16.0.0 <20"
46
46
  },
47
- "gitHead": "3e96a852db94afbbfbc6558098303a85acd6604d"
47
+ "gitHead": "6e18768b8e47066c41ae0b792bf4aa766b0b784b"
48
48
  }