@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 +135 -11
- package/build/cjs/moduleProvider/index.cjs +18 -12
- package/build/cjs/viewTransformer/amd-utils.cjs +52 -15
- package/build/cjs/viewTransformer/index.cjs +54 -48
- package/build/cjs/viewTransformer/sandbox.cjs +4 -3
- package/build/cjs/viewTransformer/ssr-element.cjs +9 -8
- package/build/es/moduleProvider/index.d.ts +1 -1
- package/build/es/moduleProvider/index.js +19 -13
- package/build/es/viewTransformer/amd-utils.d.ts +6 -2
- package/build/es/viewTransformer/amd-utils.js +64 -16
- package/build/es/viewTransformer/index.d.ts +1 -1
- package/build/es/viewTransformer/index.js +62 -60
- package/build/es/viewTransformer/sandbox-locker.d.ts +2 -5
- package/build/es/viewTransformer/sandbox-worker.d.ts +2 -5
- package/build/es/viewTransformer/sandbox.d.ts +2 -2
- package/build/es/viewTransformer/sandbox.js +5 -3
- package/build/es/viewTransformer/ssr-element.d.ts +2 -2
- package/build/es/viewTransformer/ssr-element.js +10 -10
- package/package.json +10 -10
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
|
|
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
|
-
####
|
|
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
|
-
|
|
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.
|
|
169
|
-
2.
|
|
280
|
+
1. Set a `debugger;` statement in a module.
|
|
281
|
+
2. Attach node process to the LWR server.
|
|
170
282
|
|
|
171
283
|
```sh
|
|
172
|
-
|
|
284
|
+
node --inspect-brk scripts/start.js # where start.js creates and starts the LWR server
|
|
173
285
|
```
|
|
174
286
|
|
|
175
|
-
3.
|
|
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
|

|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
44
|
-
|
|
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",
|
|
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(
|
|
87
|
-
return includedModule.replace(
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 (
|
|
33
|
-
return (0,
|
|
33
|
+
if ((0, import_shared_utils.getFeatureFlags)().SSR_SANDBOX_WORKER) {
|
|
34
|
+
return (0, import_sandbox_worker.default)(codes, context);
|
|
34
35
|
}
|
|
35
|
-
return (0,
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
61
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
|
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
|
|
2
|
-
import {
|
|
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
|
|
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',
|
|
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
|
|
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(
|
|
67
|
-
return includedModule.replace(
|
|
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 {
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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 {
|
|
2
|
-
|
|
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 {
|
|
2
|
-
|
|
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 (
|
|
5
|
-
return
|
|
5
|
+
if (getFeatureFlags().SSR_SANDBOX_WORKER) {
|
|
6
|
+
return runCodeOnWorker(codes, context);
|
|
6
7
|
}
|
|
7
|
-
|
|
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,
|
|
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:
|
|
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
|
|
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
|
-
|
|
48
|
-
|
|
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.
|
|
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.
|
|
37
|
-
"@lwrjs/diagnostics": "0.9.0-alpha.
|
|
38
|
-
"@lwrjs/shared-utils": "0.9.0-alpha.
|
|
39
|
-
"node-fetch": "^2.6.
|
|
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.
|
|
42
|
+
"@lwrjs/types": "0.9.0-alpha.30"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
|
-
"node": ">=
|
|
45
|
+
"node": ">=16.0.0 <20"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "6e18768b8e47066c41ae0b792bf4aa766b0b784b"
|
|
48
48
|
}
|