@lwrjs/lwc-ssr 0.9.0-alpha.2 → 0.9.0-alpha.20

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
@@ -43,9 +43,9 @@ SSR is activated on a per-route basis by changing `bootstrap.ssr` to `true`:
43
43
 
44
44
  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
45
 
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).
46
+ > 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
47
 
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' }`.
48
+ 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
49
 
50
50
  ```html
51
51
  <!-- my-app/src/content/page.html -->
@@ -93,6 +93,9 @@ interface PageDataResponse {
93
93
  fetchpriority?: 'high' | 'low' | 'auto';
94
94
  }[];
95
95
  };
96
+ cache?: {
97
+ ttl?: string | number; // time-to-live: time string or number of seconds
98
+ };
96
99
  }
97
100
 
98
101
  type Json = undefined | null | boolean | number | string | Json[] | { [prop: string]: Json };
@@ -108,6 +111,17 @@ Data in `PageDataResponse.props` is serialized into the page document as JSON. L
108
111
 
109
112
  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
113
 
114
+ #### Caching
115
+
116
+ Return the 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. If the root components on a page return more than one TTL, then the shortest one wins.
117
+
118
+ There are two other ways to set a TTL on a page:
119
+
120
+ 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))
121
+ 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))
122
+
123
+ LWR will use the shortest TTL value from **all** sources to set the `max-age` of the `Cache-control` header on the page.
124
+
111
125
  #### Example
112
126
 
113
127
  The `getPageData()` hook is exported as a function from a root component module:
@@ -147,6 +161,7 @@ export async function getPageData(context: SsrRequestContext): Promise<PageDataR
147
161
  },
148
162
  ],
149
163
  },
164
+ cache: { ttl: '60s' },
150
165
  };
151
166
  }
152
167
  ```
@@ -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.kebabCaseToModuleSpecifer)(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. Falling back to client-side rendering. 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,12 @@ 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} = 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
+ return {html: result, props, markup, cache};
62
61
  }
@@ -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 { kebabCaseToModuleSpecifer, 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 = kebabCaseToModuleSpecifer(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. Falling back to client-side rendering. 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,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,15 @@ 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 } = 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
+ return { html: result, props, markup, cache };
49
47
  }
50
48
  //# 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.2",
7
+ "version": "0.9.0-alpha.20",
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.2",
38
- "@lwrjs/shared-utils": "0.9.0-alpha.2",
39
- "node-fetch": "^2.6.1"
36
+ "@locker/near-membrane-node": "^0.12.0",
37
+ "@lwrjs/diagnostics": "0.9.0-alpha.20",
38
+ "@lwrjs/shared-utils": "0.9.0-alpha.20",
39
+ "node-fetch": "^2.6.8"
40
40
  },
41
41
  "devDependencies": {
42
- "@lwrjs/types": "0.9.0-alpha.2"
42
+ "@lwrjs/types": "0.9.0-alpha.20"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=14.15.4 <19"
46
46
  },
47
- "gitHead": "bcf63de23d1a2a53fb0e961dba4396e8753710dd"
47
+ "gitHead": "b01b6beb86cae6c28308c373db15cb1a4f42a0ee"
48
48
  }