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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,9 +7,11 @@
7
7
  - [Add the SSR package dependency](#add-the-ssr-package-dependency)
8
8
  - [Turn on SSR](#turn-on-ssr)
9
9
  - [Building SSR pages](#building-ssr-pages)
10
+ - [SSR detection](#ssr-detection)
10
11
  - [Loading data during SSR](#loading-data-during-ssr)
11
12
  - [Caching](#caching)
12
13
  - [Client hydration](#client-hydration)
14
+ - [Skip SSR](#skip-ssr)
13
15
  - [Limitations](#limitations)
14
16
  - [Synthetic shadow](#synthetic-shadow)
15
17
  - [Locker](#locker)
@@ -18,6 +20,7 @@
18
20
  - [SSR execution](#ssr-execution)
19
21
  - [Portability](#portability)
20
22
  - [Synchronous code](#synchronous-code)
23
+ - [Styling](#styling)
21
24
  - [Debugging](#debugging)
22
25
  - [Debug logging]()
23
26
  - [Breakpoints](#breakpoints)
@@ -27,7 +30,7 @@
27
30
 
28
31
  ### What is SSR?
29
32
 
30
- [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.
33
+ [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 may be ["hydrated"](#client-hydration) to create an interactive web app.
31
34
 
32
35
  See the following RFCs from LWC:
33
36
 
@@ -52,7 +55,7 @@ Add SSR capabilities to an LWR app by including `@lwrjs/lwc-ssr` in its _package
52
55
  // my-app/package.json
53
56
  {
54
57
  "dependencies": {
55
- "@lwrjs/lwc-ssr": "0.9.0"
58
+ "@lwrjs/lwc-ssr": "latest"
56
59
  }
57
60
  }
58
61
  ```
@@ -98,6 +101,35 @@ LWR will automatically pass any root component attributes from a [template](http
98
101
  </section>
99
102
  ```
100
103
 
104
+ ### SSR detection
105
+
106
+ Check the `import.meta.env.SSR` flag to determine where component code is running. On the server it will be `true`, and on the client it will be [falsy](https://medium.com/coding-at-dawn/what-are-falsy-values-in-javascript-ca0faa34feb4). Component developers can use this flag to control in which environment code is run:
107
+
108
+ ```js
109
+ // pivot on the SSR flag
110
+ export default class App extends LightningElement {
111
+ connectedCallback() {
112
+ if (import.meta.env.SSR) {
113
+ // run code on the server
114
+ } else {
115
+ // run code on the client
116
+ }
117
+ }
118
+ }
119
+ ```
120
+
121
+ When using typescript, LWR augments the global `ImportMeta` interface in the `@lwrjs/types` package:
122
+
123
+ ```ts
124
+ declare global {
125
+ interface ImportMeta {
126
+ readonly env: {
127
+ SSR: boolean;
128
+ };
129
+ }
130
+ }
131
+ ```
132
+
101
133
  ### Loading data during SSR
102
134
 
103
135
  Many components depend on external data and resources. LWR provides a `getPageData()` hook for developers to fetch data on the server. During SSR, LWR calls the `getPageData()` hook for each **root component**, then serializes the resulting data into the page document as either [JSON](#json) or [markup](#markup).
@@ -142,7 +174,9 @@ In LWR, the SSR process runs in a sandbox. This sandbox supports `globalThis.fet
142
174
 
143
175
  #### JSON
144
176
 
145
- 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).
177
+ LWR passes this data to the root component as [public properties](<(https://developer.salesforce.com/docs/component-library/documentation/en/lwc/reactivity_public)>) during SSR. In order to receive the properties, the root component must declare them using the `@api` decorator (see the `data` property in the [example](#data-example) below).
178
+
179
+ If a root component is [hydrated](#client-hydration), its `PageDataResponse.props` are serialized into the page document as JSON, then passed in during client hydration.
146
180
 
147
181
  #### Markup
148
182
 
@@ -211,7 +245,35 @@ LWR will use the shortest TTL value from **all** sources to set the `max-age` of
211
245
 
212
246
  ### Client hydration
213
247
 
214
- 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.
248
+ Root components can opt-in to hydrate their SSRed HTML in the browser. 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. To opt-in to hydration, a root component must have the `lwr:hydrate` directive:
249
+
250
+ ```html
251
+ <!-- my-app/src/content/contact.html -->
252
+
253
+ <!-- hydrated -->
254
+ <my-contact lwr:hydrate></my-contact>
255
+
256
+ <!-- NOT hydrated -->
257
+ <my-about></my-about>
258
+ ```
259
+
260
+ LWC will log a "hydration warning" if the SSRed component HTML does not match the output of its _first rendering cycle_ on the client. Hydration warnings should be prevented because they result in unsightly UI shifting. To avoid hydration warnings, ensure that template updates only occur due to user interaction, data fetching, or other _asynchronous_ actions.
261
+
262
+ ### Skip SSR
263
+
264
+ Root components can skip SSR by setting the `lwr:hydrate` directive to `client-only`:
265
+
266
+ ```html
267
+ <!-- my-app/src/content/location.html -->
268
+
269
+ <!-- SSRed -->
270
+ <my-info></my-info>
271
+
272
+ <!-- NOT SSRed -->
273
+ <my-map lwr:hydrate="client-only"></my-map>
274
+ ```
275
+
276
+ Root components which opt-out of SSR will be fully rendered on the client using the [LWC `createElement()` API](https://www.npmjs.com/package/@lwc/engine-dom).
215
277
 
216
278
  ## Limitations
217
279
 
@@ -264,20 +326,20 @@ Examples of non-portable code and objects include:
264
326
  - `window`
265
327
  - `document`
266
328
  - selector functions (`template.querySelector`, `template.querySelectorAll`, ...)
267
- - `classList`
268
329
  - JavaScript eventing
269
330
 
270
331
  Isomorphic components can:
271
332
 
272
333
  - place non-portable code in a function which is not [executed during SSR](#ssr-execution), such as the `renderedCallback`.
334
+ - import non-portable code [dynamically](#synchronous-code), so it does not get executed on the server.
273
335
  - choose LWC APIs which are supported on both the client and server (eg: [`lwc:inner-html`](https://rfcs.lwc.dev/rfcs/lwc/0123-innerhtml-binding) over `appendChild()`).
274
- - guard non-portable objects so they are not accessed during SSR:
336
+ - guard non-portable objects so they are not accessed during SSR using the [`import.meta.env.SSR` flag](#ssr-detection):
275
337
 
276
338
  ```js
277
339
  // guard usage of the window object so it does not throw during SSR
278
340
  export default class App extends LightningElement {
279
341
  connectedCallback() {
280
- if (typeof window !== 'undefined') {
342
+ if (!import.meta.env.SSR) {
281
343
  window.addEventListener('error', (evt) => {
282
344
  console.error(`⚠️ Uncaught error: ${evt.message}`);
283
345
  });
@@ -299,6 +361,10 @@ The LWC SSR process run in a single synchronous pass. So any asynchronous code *
299
361
  - [`async`/`await`](https://www.w3schools.com/js/js_async.asp)
300
362
  - [dynamic imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import)
301
363
 
364
+ ### Styling
365
+
366
+ To render correctly, SSRed components must use either [native shadow](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.create_mixed_shadow) or [light DOM](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.create_light_dom). SSRed components which depend on [synthetic shadow](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.create_dom) may not be styled correctly on the client. If they opt-in to [hydration](#client-hydration), warnings will be thrown on the client.
367
+
302
368
  ## Debugging
303
369
 
304
370
  ### Debug logging
@@ -35,25 +35,48 @@ import { renderComponent } from '@lwc/engine-server';
35
35
  import Ctor, * as rootComponent from '${rootSpecifier}';
36
36
 
37
37
  (async () => {
38
- let result, props, markup;
38
+ let result, props, markup, existingTaskCount;
39
39
  try {
40
40
  // 1. setup page data
41
41
  const context = globalThis.getContext();
42
42
  props = context.props;
43
43
  if (rootComponent.getPageData) {
44
- const data = await rootComponent.getPageData(context);
44
+ const data = await globalThis.trace({
45
+ name: 'lwr.view.getPageData',
46
+ attributes: {
47
+ specifier: '${rootSpecifier}'
48
+ }
49
+ }, () => rootComponent.getPageData(context));
50
+
45
51
  props = data.props; // overwrite public props
46
52
  markup = data.markup;
47
53
  }
48
54
 
55
+ existingTaskCount = process.getActiveResourcesInfo
56
+ ? process.getActiveResourcesInfo().length
57
+ : 0;
58
+
49
59
  // 2. render component
50
- result = renderComponent('${(0, import_shared_utils.moduleSpecifierToKebabCase)(rootSpecifier)}', Ctor, props || {});
60
+ result = globalThis.trace({
61
+ name: 'lwr.view.renderComponent',
62
+ attributes: {
63
+ specifier: '${rootSpecifier}'
64
+ }
65
+ }, () => renderComponent('${(0, import_shared_utils.moduleSpecifierToKebabCase)(rootSpecifier)}', Ctor, props || {}));
51
66
 
52
67
  } catch(e) {
53
68
  // (3) relay error
54
69
  globalThis.resolver({ error: e.message || e });
55
70
  }
56
71
 
72
+ const currentTaskCount = process.getActiveResourcesInfo
73
+ ? process.getActiveResourcesInfo().length
74
+ : 0;
75
+
76
+ if (currentTaskCount - existingTaskCount > 0) {
77
+ console.warn('[warn] async tasks encountered while server rendering "${rootSpecifier}"');
78
+ }
79
+
57
80
  // 3. relay successful results
58
81
  globalThis.resolver({ result, props, markup });
59
82
  })()`;
@@ -29,7 +29,7 @@ __export(exports, {
29
29
  });
30
30
  var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
31
31
  var import_identity = __toModule(require("../identity.cjs"));
32
- var lwcEngineSpecifier = "@lwc/engine-server";
32
+ var LWC_SPECIFIERS = {csr: "lwc", ssr: "@lwc/engine-server"};
33
33
  async function readableToString(readable) {
34
34
  let result = "";
35
35
  for await (const chunk of readable) {
@@ -70,27 +70,23 @@ function getLwrConfig(bundleSpecifier, lwrVersion) {
70
70
  }
71
71
  });
72
72
  }
73
- function lwcDefineOverride(lwcSpecifier) {
74
- return `LWR.define("${lwcSpecifier}", ["${lwcSpecifier.replace("lwc", lwcEngineSpecifier)}"], function(lwcEngine) { return lwcEngine; });`;
73
+ function aliasLwcEngine(ssrSpecifier, aliases) {
74
+ const csrSpecifier = ssrSpecifier.replace(aliases.ssr, aliases.csr);
75
+ const ssrLwcAlias = (0, import_shared_utils.createAmdAlias)(csrSpecifier, ssrSpecifier);
76
+ const unversionedAlias = (0, import_shared_utils.createAmdAlias)(LWC_SPECIFIERS.csr, ssrSpecifier);
77
+ return `${ssrLwcAlias}${unversionedAlias}`;
75
78
  }
76
79
  var GLOBALTHIS_LWR = `globalThis.LWR = globalThis.LWR || {};`;
77
80
  async function getCode(runtimeEnvironment, lwrVersion, bundleSpecifier, includedModules, resourceRegistry) {
78
81
  const loaderShimSource2 = await getLoaderShim(resourceRegistry, runtimeEnvironment);
79
82
  const lwrConfigString = JSON.stringify(getLwrConfig(bundleSpecifier, lwrVersion));
80
- const lwcSpecifier = includedModules.reduce((specifier, includedModule) => {
81
- if (includedModule.startsWith("lwc/v")) {
82
- return includedModule;
83
- }
84
- if (!specifier && includedModule.startsWith(`${lwcEngineSpecifier}/v`)) {
85
- return includedModule.replace(lwcEngineSpecifier, "lwc");
86
- }
87
- return specifier;
88
- }, "");
83
+ const lwcSpecifier = includedModules.find((m) => m.startsWith(`${LWC_SPECIFIERS.ssr}/v`));
89
84
  return [
90
85
  GLOBALTHIS_LWR,
91
86
  `Object.assign(globalThis.LWR, ${lwrConfigString});`,
87
+ `Object.assign(globalThis.LWR, { onError: (err) => globalThis.resolver({ error: err.message })});`,
92
88
  loaderShimSource2 ? loaderShimSource2 : "",
93
- lwcSpecifier ? lwcDefineOverride(lwcSpecifier) : ""
89
+ lwcSpecifier ? aliasLwcEngine(lwcSpecifier, LWC_SPECIFIERS) : ""
94
90
  ];
95
91
  }
96
92
  async function getBundle(specifier, moduleBundler, runtimeEnvironment) {
@@ -102,29 +98,48 @@ async function getBundle(specifier, moduleBundler, runtimeEnvironment) {
102
98
  async function bundle(specifier, moduleBundler, runtimeEnvironment, bundleConfigOverrides) {
103
99
  return await moduleBundler.getModuleBundle({specifier}, runtimeEnvironment, void 0, bundleConfigOverrides);
104
100
  }
101
+ async function bundleImports(bundleCode, imports = [], visited, moduleBundler, runtimeEnvironment) {
102
+ for (const {specifier} of imports) {
103
+ if (!visited.has(specifier)) {
104
+ visited.add(specifier);
105
+ const {code, bundleRecord, version} = await bundle(specifier, moduleBundler, runtimeEnvironment);
106
+ let bundledCode;
107
+ if (runtimeEnvironment.featureFlags?.EXPERIMENTAL_UNVERSIONED_ALIASES) {
108
+ const versionedSpecifier = (0, import_shared_utils.getSpecifier)({specifier, version});
109
+ const aliasCode = (0, import_shared_utils.createAmdAlias)(versionedSpecifier, specifier);
110
+ bundledCode = [code, aliasCode].filter(Boolean).join("");
111
+ } else {
112
+ bundledCode = code;
113
+ }
114
+ bundleCode = await bundleImports(bundledCode, bundleRecord.imports, visited, moduleBundler, runtimeEnvironment) + bundleCode;
115
+ }
116
+ }
117
+ return bundleCode;
118
+ }
105
119
  async function buildBundle(ssrSpecifier, moduleBundler, runtimeEnvironment) {
106
120
  const rootSpecifier = ssrSpecifier.replace(import_identity.LWC_SSR_PREFIX, "");
107
121
  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
- }
122
+ let rootCode = await bundleImports(_rootCode, rootBundleRecord.imports, new Set(["lwc", rootSpecifier]), moduleBundler, runtimeEnvironment);
123
+ if (runtimeEnvironment.featureFlags?.EXPERIMENTAL_UNVERSIONED_ALIASES) {
124
+ const aliasSpecifier = (0, import_shared_utils.getSpecifier)({
125
+ specifier: rootSpecifier,
126
+ version: "version-not-provided"
127
+ });
128
+ const aliasCode = (0, import_shared_utils.createAmdAlias)(aliasSpecifier, rootSpecifier);
129
+ rootCode = [aliasCode, rootCode].join("");
116
130
  }
117
- const {code: lwcEngineCode, version: lwcVersion} = await bundle(lwcEngineSpecifier, moduleBundler, runtimeEnvironment);
131
+ const {code: lwcEngineCode, version: lwcVersion} = await bundle(LWC_SPECIFIERS.ssr, moduleBundler, runtimeEnvironment);
118
132
  const {
119
133
  bundleRecord,
120
134
  code: ssrCode,
121
135
  version: lwrVersion
122
136
  } = await bundle(ssrSpecifier, moduleBundler, runtimeEnvironment, {
123
137
  appendExcludes: true,
124
- exclude: [lwcEngineSpecifier, rootSpecifier]
138
+ exclude: [LWC_SPECIFIERS.ssr, rootSpecifier]
125
139
  });
126
140
  const code = rootCode + lwcEngineCode + ssrCode;
127
- bundleRecord.includedModules.push(`${lwcEngineSpecifier}/v/${(0, import_shared_utils.normalizeVersionToUri)(lwcVersion)}`);
141
+ bundleRecord.includedModules.push(...rootBundleRecord.includedModules);
142
+ bundleRecord.includedModules.push(`${LWC_SPECIFIERS.ssr}/v/${(0, import_shared_utils.normalizeVersionToUri)(lwcVersion)}`);
128
143
  return {
129
144
  bundleRecord,
130
145
  code,
@@ -24,26 +24,32 @@ var __toModule = (module2) => {
24
24
  // packages/@lwrjs/lwc-ssr/src/viewTransformer/index.ts
25
25
  __markAsModule(exports);
26
26
  __export(exports, {
27
- default: () => lwcSsrViewTranformer
27
+ default: () => lwcSsrViewTransformer
28
28
  });
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, resourceRegistry}) {
32
+ function lwcSsrViewTransformer(options, {moduleBundler, resourceRegistry}) {
33
33
  return {
34
34
  name: "ssr-lwc-transformer",
35
35
  async link(stringBuilder, viewContext, {customElements}) {
36
36
  if (!viewContext.view.bootstrap?.ssr) {
37
37
  return {};
38
38
  }
39
- import_shared_utils.logger.debug("[lwcSsrViewTranformer] link");
40
- import_shared_utils.logger.verbose("[lwcSsrViewTranformer] link input", stringBuilder);
39
+ import_shared_utils.logger.debug("[lwcSsrViewTransformer] link");
40
+ import_shared_utils.logger.verbose("[lwcSsrViewTransformer] link input", stringBuilder);
41
41
  const ssrModules = [];
42
- for (const {tagName, location, props} of customElements) {
43
- if (location) {
42
+ for (const [index, {tagName, location, props}] of customElements.entries()) {
43
+ const isCsr = (0, import_shared_utils.isCsrIsland)(props);
44
+ if (isCsr && location) {
45
+ const {startOffset, endOffset} = location;
46
+ stringBuilder.overwrite(startOffset, endOffset, `<${tagName}></${tagName}>`);
47
+ }
48
+ if (!isCsr && location) {
44
49
  const {startOffset, endOffset} = location;
45
50
  const moduleSpecifier = (0, import_shared_utils.kebabCaseToModuleSpecifier)(tagName);
46
51
  ssrModules.push({
52
+ index,
47
53
  startOffset,
48
54
  endOffset,
49
55
  props,
@@ -55,18 +61,25 @@ function lwcSsrViewTranformer(options, {moduleBundler, resourceRegistry}) {
55
61
  const ssrProps = {};
56
62
  let ssrLinks = "";
57
63
  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(({
64
+ await Promise.all(ssrModules.map(({index, specifier, tagName, props: rawProps = {}, startOffset, endOffset}) => {
65
+ const hydrate = (0, import_shared_utils.isHydrateOnLoad)(rawProps);
66
+ const passProps = {...rawProps};
67
+ delete passProps[import_shared_utils.HYDRATE_DIRECTIVE];
68
+ return (0, import_ssr_element.ssrElement)({specifier, props: passProps}, moduleBundler, resourceRegistry, viewContext).then(({
60
69
  html,
61
- props: props2 = {},
70
+ props = {},
62
71
  markup: {links = []} = {links: []},
63
72
  cache: {ttl} = {}
64
73
  }) => {
65
74
  pageTtl = (0, import_shared_utils.shortestTtl)(ttl, pageTtl);
66
- const propsId = (0, import_identity.getPropsId)();
67
- ssrProps[propsId] = props2;
75
+ let propsAttr = "";
76
+ if (hydrate) {
77
+ const propsId = (0, import_identity.getPropsId)();
78
+ propsAttr = ` ${import_identity.SSR_PROPS_ATTR}="${propsId}"`;
79
+ ssrProps[propsId] = props;
80
+ }
68
81
  const [, remain] = html.split(`<${tagName}`);
69
- html = [`<${tagName}`, ` ${import_identity.SSR_PROPS_ATTR}="${propsId}"`, remain].join("");
82
+ html = [`<${tagName}`, propsAttr, remain].join("");
70
83
  links.forEach(({href, rel, as, fetchpriority}) => {
71
84
  const relStr = rel ? ` rel="${rel}"` : "", asStr = as ? ` as="${as}"` : "", fetchStr = fetchpriority ? ` fetchpriority="${fetchpriority}"` : "";
72
85
  ssrLinks += `<link href="${href}"${relStr}${asStr}${fetchStr}>
@@ -74,7 +87,10 @@ function lwcSsrViewTranformer(options, {moduleBundler, resourceRegistry}) {
74
87
  });
75
88
  stringBuilder.overwrite(startOffset, endOffset, html);
76
89
  }).catch((err) => {
77
- import_shared_utils.logger.warn(`Server-side rendering for "${specifier}" failed. Reason: `, err.stack);
90
+ customElements[index].props === void 0 ? customElements[index].props = {
91
+ [import_shared_utils.HYDRATE_DIRECTIVE]: import_shared_utils.HYDRATE_CLIENT_VALUE
92
+ } : customElements[index].props[import_shared_utils.HYDRATE_DIRECTIVE] = import_shared_utils.HYDRATE_CLIENT_VALUE;
93
+ import_shared_utils.logger.warn(`Server-side rendering for "${specifier}" failed. Falling back to client-side rendering. Reason: `, err.stack);
78
94
  });
79
95
  }));
80
96
  if (Object.keys(ssrProps).length) {
@@ -88,7 +104,7 @@ function lwcSsrViewTranformer(options, {moduleBundler, resourceRegistry}) {
88
104
  import_shared_utils.logger.error("Adding links during server-side rendering failed. Could not find the </head> tag.");
89
105
  }
90
106
  }
91
- import_shared_utils.logger.verbose("lwcSsrViewTranformer response", stringBuilder);
107
+ import_shared_utils.logger.verbose("lwcSsrViewTransformer response", stringBuilder);
92
108
  return {cache: {ttl: pageTtl}};
93
109
  }
94
110
  };
@@ -26,8 +26,10 @@ __markAsModule(exports);
26
26
  __export(exports, {
27
27
  default: () => runCode
28
28
  });
29
+ var import_instrumentation = __toModule(require("@lwrjs/instrumentation"));
29
30
  var import_near_membrane_node = __toModule(require("@locker/near-membrane-node"));
30
31
  var import_node_fetch = __toModule(require("node-fetch"));
32
+ var import_crypto = __toModule(require("crypto"));
31
33
  function runCode(codes, context) {
32
34
  return new Promise((resolve, reject) => {
33
35
  let resolver;
@@ -35,8 +37,13 @@ function runCode(codes, context) {
35
37
  function getContext() {
36
38
  return context;
37
39
  }
40
+ const tracer = (0, import_instrumentation.getTracer)();
41
+ const trace = tracer.trace.bind(tracer);
38
42
  const endowments = Object.getOwnPropertyDescriptors({
43
+ AbortController,
44
+ crypto: import_crypto.webcrypto,
39
45
  getContext,
46
+ trace,
40
47
  fetch: import_node_fetch.default,
41
48
  resolver,
42
49
  process,
@@ -32,7 +32,9 @@ var WORKER_CODE_SANDBOX_APIS = [
32
32
  `const { parentPort, workerData } = require('worker_threads');`,
33
33
  `globalThis.getContext = () => workerData;`,
34
34
  `globalThis.fetch = require('node-fetch');`,
35
- `globalThis.resolver = (...args) => parentPort.postMessage(...args);`
35
+ `globalThis.crypto = require('crypto').webcrypto;`,
36
+ `globalThis.resolver = (...args) => parentPort.postMessage(...args);`,
37
+ `globalThis.trace = (id, fn) => fn()`
36
38
  ];
37
39
  function runCode(codes, workerData) {
38
40
  const workerCode = [HEADER, ...WORKER_CODE_SANDBOX_APIS, ...codes].join("\n");
@@ -30,6 +30,8 @@ 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
+ var import_instrumentation = __toModule(require("@lwrjs/instrumentation"));
34
+ var SSR_FLAG = "process.env.SSR = true;";
33
35
  async function ssrElement({specifier, props: templateProps}, moduleBundler, resourceRegistry, {runtimeEnvironment, runtimeParams}) {
34
36
  const {format} = runtimeEnvironment;
35
37
  const {
@@ -50,14 +52,22 @@ async function ssrElement({specifier, props: templateProps}, moduleBundler, reso
50
52
  locale: runtimeParams.locale || runtimeEnvironment.defaultLocale
51
53
  };
52
54
  const startTime = import_perf_hooks.performance.now();
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),
55
- code
56
- ], context) : await (0, import_sandbox.default)([code], context);
57
- const endTime = import_perf_hooks.performance.now();
58
- const timeDiff = endTime - startTime;
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};
55
+ return (0, import_instrumentation.getTracer)().trace({
56
+ name: import_instrumentation.ViewSpan.ServerSideRender,
57
+ attributes: {
58
+ specifier: bundleSpecifier
59
+ }
60
+ }, async () => {
61
+ const {result, props, markup, cache, error} = format === "amd" ? await (0, import_sandbox.default)([
62
+ SSR_FLAG,
63
+ ...await (0, import_amd_utils.getCode)(runtimeEnvironment, version.replace(/\./g, "_"), bundleSpecifier, bundleRecord.includedModules, resourceRegistry),
64
+ code
65
+ ], context) : await (0, import_sandbox.default)([SSR_FLAG, code], context);
66
+ const endTime = import_perf_hooks.performance.now();
67
+ const timeDiff = endTime - startTime;
68
+ import_shared_utils.logger.info(`[Server-side Rendering] ${specifier} in ${Math.round(timeDiff)} ms`);
69
+ if (error)
70
+ throw new Error(error);
71
+ return {html: result, props, markup, cache};
72
+ });
63
73
  }
@@ -1,4 +1,4 @@
1
- import { ModuleCompiled, ModuleEntry, ModuleProvider, ProviderContext, AbstractModuleId } from '@lwrjs/types';
1
+ import type { ModuleCompiled, ModuleEntry, ModuleProvider, ProviderContext, AbstractModuleId } from '@lwrjs/types';
2
2
  /**
3
3
  * This module provider generates code which server-side renders a given component.
4
4
  * It handles module specifiers in the form: "@lwrjs/lwc-ssr/${component specifier}".
@@ -19,25 +19,48 @@ import { renderComponent } from '@lwc/engine-server';
19
19
  import Ctor, * as rootComponent from '${rootSpecifier}';
20
20
 
21
21
  (async () => {
22
- let result, props, markup;
22
+ let result, props, markup, existingTaskCount;
23
23
  try {
24
24
  // 1. setup page data
25
25
  const context = globalThis.getContext();
26
26
  props = context.props;
27
27
  if (rootComponent.getPageData) {
28
- const data = await rootComponent.getPageData(context);
28
+ const data = await globalThis.trace({
29
+ name: 'lwr.view.getPageData',
30
+ attributes: {
31
+ specifier: '${rootSpecifier}'
32
+ }
33
+ }, () => rootComponent.getPageData(context));
34
+
29
35
  props = data.props; // overwrite public props
30
36
  markup = data.markup;
31
37
  }
32
38
 
39
+ existingTaskCount = process.getActiveResourcesInfo
40
+ ? process.getActiveResourcesInfo().length
41
+ : 0;
42
+
33
43
  // 2. render component
34
- result = renderComponent('${moduleSpecifierToKebabCase(rootSpecifier)}', Ctor, props || {});
44
+ result = globalThis.trace({
45
+ name: 'lwr.view.renderComponent',
46
+ attributes: {
47
+ specifier: '${rootSpecifier}'
48
+ }
49
+ }, () => renderComponent('${moduleSpecifierToKebabCase(rootSpecifier)}', Ctor, props || {}));
35
50
 
36
51
  } catch(e) {
37
52
  // (3) relay error
38
53
  globalThis.resolver({ error: e.message || e });
39
54
  }
40
55
 
56
+ const currentTaskCount = process.getActiveResourcesInfo
57
+ ? process.getActiveResourcesInfo().length
58
+ : 0;
59
+
60
+ if (currentTaskCount - existingTaskCount > 0) {
61
+ console.warn('[warn] async tasks encountered while server rendering "${rootSpecifier}"');
62
+ }
63
+
41
64
  // 3. relay successful results
42
65
  globalThis.resolver({ result, props, markup });
43
66
  })()`;
@@ -1,6 +1,6 @@
1
- import { getFeatureFlags, normalizeVersionToUri } from '@lwrjs/shared-utils';
1
+ import { createAmdAlias, getFeatureFlags, getSpecifier, normalizeVersionToUri } from '@lwrjs/shared-utils';
2
2
  import { LWC_SSR_PREFIX } from '../identity.js';
3
- const lwcEngineSpecifier = '@lwc/engine-server';
3
+ const LWC_SPECIFIERS = { csr: 'lwc', ssr: '@lwc/engine-server' };
4
4
  async function readableToString(readable) {
5
5
  let result = '';
6
6
  for await (const chunk of readable) {
@@ -46,33 +46,30 @@ function getLwrConfig(bundleSpecifier, lwrVersion) {
46
46
  },
47
47
  });
48
48
  }
49
- // lwc/v/2_13_0 -> @lwc/engine-server/v/2_13_0
50
- function lwcDefineOverride(lwcSpecifier) {
51
- return `LWR.define("${lwcSpecifier}", ["${lwcSpecifier.replace('lwc', lwcEngineSpecifier)}"], function(lwcEngine) { return lwcEngine; });`;
49
+ // Alias a CSR module specifier to its SSR implementation
50
+ // eg: lwc/v/x_y_z -> @lwc/engine-server/v/x_y_z
51
+ function aliasLwcEngine(ssrSpecifier, aliases) {
52
+ const csrSpecifier = ssrSpecifier.replace(aliases.ssr, aliases.csr);
53
+ const ssrLwcAlias = createAmdAlias(csrSpecifier, ssrSpecifier);
54
+ // TODO: remove pending TD-0148452
55
+ const unversionedAlias = createAmdAlias(LWC_SPECIFIERS.csr, ssrSpecifier);
56
+ return `${ssrLwcAlias}${unversionedAlias}`;
52
57
  }
53
58
  const GLOBALTHIS_LWR = `globalThis.LWR = globalThis.LWR || {};`;
54
59
  export async function getCode(runtimeEnvironment, lwrVersion, bundleSpecifier, includedModules, resourceRegistry) {
55
60
  const loaderShimSource = await getLoaderShim(resourceRegistry, runtimeEnvironment);
56
61
  const lwrConfigString = JSON.stringify(getLwrConfig(bundleSpecifier, lwrVersion));
57
- const lwcSpecifier = includedModules.reduce((specifier, includedModule) => {
58
- if (includedModule.startsWith('lwc/v')) {
59
- return includedModule;
60
- }
61
- // define `lwc` when `@lwc/engine-server` is included in the bundle
62
- // `lwc` will be excluded by default when bundling is enabled
63
- if (!specifier && includedModule.startsWith(`${lwcEngineSpecifier}/v`)) {
64
- return includedModule.replace(lwcEngineSpecifier, 'lwc');
65
- }
66
- return specifier;
67
- }, '');
62
+ const lwcSpecifier = includedModules.find((m) => m.startsWith(`${LWC_SPECIFIERS.ssr}/v`));
68
63
  // Order matters:
69
64
  // 1. "globalThis.LWR" must be defined prior to executing the shim and loader
70
65
  // 2. the lwc module override needs to be defined before lwc is [re]defined in the custom element code (first define wins)
71
66
  return [
72
67
  GLOBALTHIS_LWR,
73
68
  `Object.assign(globalThis.LWR, ${lwrConfigString});`,
69
+ // attaching a custom error handler to catch async bootstrap errors
70
+ `Object.assign(globalThis.LWR, { onError: (err) => globalThis.resolver({ error: err.message })});`,
74
71
  loaderShimSource ? loaderShimSource : '',
75
- lwcSpecifier ? lwcDefineOverride(lwcSpecifier) : '',
72
+ lwcSpecifier ? aliasLwcEngine(lwcSpecifier, LWC_SPECIFIERS) : '',
76
73
  ];
77
74
  }
78
75
  export async function getBundle(specifier, // e.g. "@lwrjs/lwc-ssr/root/component"
@@ -88,37 +85,62 @@ moduleBundler, runtimeEnvironment) {
88
85
  async function bundle(specifier, moduleBundler, runtimeEnvironment, bundleConfigOverrides) {
89
86
  return await moduleBundler.getModuleBundle({ specifier }, runtimeEnvironment, undefined, bundleConfigOverrides);
90
87
  }
88
+ // Recursively bundle the static imports of a root bundle into a single bundle
89
+ async function bundleImports(bundleCode, imports = [], visited, moduleBundler, runtimeEnvironment) {
90
+ for (const { specifier } of imports) {
91
+ if (!visited.has(specifier)) {
92
+ visited.add(specifier);
93
+ // eslint-disable-next-line no-await-in-loop
94
+ const { code, bundleRecord, version } = await bundle(specifier, moduleBundler, runtimeEnvironment);
95
+ let bundledCode;
96
+ if (runtimeEnvironment.featureFlags?.EXPERIMENTAL_UNVERSIONED_ALIASES) {
97
+ const versionedSpecifier = getSpecifier({ specifier, version });
98
+ const aliasCode = createAmdAlias(versionedSpecifier, specifier);
99
+ bundledCode = [code, aliasCode].filter(Boolean).join('');
100
+ }
101
+ else {
102
+ bundledCode = code;
103
+ }
104
+ bundleCode =
105
+ // eslint-disable-next-line no-await-in-loop
106
+ (await bundleImports(bundledCode, bundleRecord.imports, visited, moduleBundler, runtimeEnvironment)) + bundleCode;
107
+ }
108
+ }
109
+ return bundleCode;
110
+ }
91
111
  // Build a SSR bundle for a root component by concatenating:
92
- // - the root component bundle
112
+ // - the root component bundle and static imports
93
113
  // - @lwc/engine-server
94
114
  // - the SSR bundle for the root specifier
95
115
  async function buildBundle(ssrSpecifier, moduleBundler, runtimeEnvironment) {
96
- // 1. Get the bundle for the root component
116
+ // 1. Get the bundle for the root component, including all static dependencies
97
117
  const rootSpecifier = ssrSpecifier.replace(LWC_SSR_PREFIX, '');
98
118
  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
- }
119
+ let rootCode = await bundleImports(_rootCode, rootBundleRecord.imports, new Set(['lwc', rootSpecifier]), // visited (lwc is excluded)
120
+ moduleBundler, runtimeEnvironment);
121
+ // TODO: remove pending TD-0148452
122
+ if (runtimeEnvironment.featureFlags?.EXPERIMENTAL_UNVERSIONED_ALIASES) {
123
+ // Create an AMD alias to the unversioned root module
124
+ // if the bundle-metadata for the module doesn't have a version, it will have /v/version-not-provided`
125
+ const aliasSpecifier = getSpecifier({
126
+ specifier: rootSpecifier,
127
+ version: 'version-not-provided',
128
+ });
129
+ const aliasCode = createAmdAlias(aliasSpecifier, rootSpecifier);
130
+ rootCode = [aliasCode, rootCode].join('');
110
131
  }
111
132
  // 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
133
+ const { code: lwcEngineCode, version: lwcVersion } = await bundle(LWC_SPECIFIERS.ssr, moduleBundler, runtimeEnvironment);
134
+ // 3. Get the bundle for the SSR root component
114
135
  // Exclude the LWC engine and root component from this bundle. Use the pre-built bundles fetched above instead.
115
136
  const { bundleRecord, code: ssrCode, version: lwrVersion, } = await bundle(ssrSpecifier, moduleBundler, runtimeEnvironment, {
116
137
  appendExcludes: true,
117
- exclude: [lwcEngineSpecifier, rootSpecifier],
138
+ exclude: [LWC_SPECIFIERS.ssr, rootSpecifier],
118
139
  });
119
140
  // Now concatenate the code gathered above to create the SSR bundle for the root component
120
141
  const code = rootCode + lwcEngineCode + ssrCode;
121
- bundleRecord.includedModules.push(`${lwcEngineSpecifier}/v/${normalizeVersionToUri(lwcVersion)}`); // for LWC aliasing
142
+ bundleRecord.includedModules.push(...rootBundleRecord.includedModules); // add the root component's inclusions
143
+ bundleRecord.includedModules.push(`${LWC_SPECIFIERS.ssr}/v/${normalizeVersionToUri(lwcVersion)}`); // for LWC aliasing
122
144
  return {
123
145
  bundleRecord,
124
146
  code,
@@ -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/init" module will hydrate ALL the custom elements on the page
22
22
  */
23
- export default function lwcSsrViewTranformer(options: SsrPluginOptions, { moduleBundler, resourceRegistry }: ProviderContext): ViewTransformPlugin;
23
+ export default function lwcSsrViewTransformer(options: SsrPluginOptions, { moduleBundler, resourceRegistry }: ProviderContext): ViewTransformPlugin;
24
24
  export {};
25
25
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,4 @@
1
- import { kebabCaseToModuleSpecifier, logger, shortestTtl } from '@lwrjs/shared-utils';
1
+ import { HYDRATE_CLIENT_VALUE, HYDRATE_DIRECTIVE, isCsrIsland, isHydrateOnLoad, 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,22 +19,29 @@ 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/init" module will hydrate ALL the custom elements on the page
21
21
  */
22
- export default function lwcSsrViewTranformer(options, { moduleBundler, resourceRegistry }) {
22
+ export default function lwcSsrViewTransformer(options, { moduleBundler, resourceRegistry }) {
23
23
  return {
24
24
  name: 'ssr-lwc-transformer',
25
25
  async link(stringBuilder, viewContext, { customElements }) {
26
26
  if (!viewContext.view.bootstrap?.ssr) {
27
27
  return {}; // no SSR
28
28
  }
29
- logger.debug('[lwcSsrViewTranformer] link');
30
- logger.verbose('[lwcSsrViewTranformer] link input', stringBuilder);
29
+ logger.debug('[lwcSsrViewTransformer] link');
30
+ logger.verbose('[lwcSsrViewTransformer] link input', stringBuilder);
31
31
  // Gather all the SSRable custom elements (ie: root components) into 1 list
32
32
  const ssrModules = [];
33
- for (const { tagName, location, props } of customElements) {
34
- if (location) {
33
+ for (const [index, { tagName, location, props }] of customElements.entries()) {
34
+ const isCsr = isCsrIsland(props);
35
+ if (isCsr && location) {
36
+ // Strip the CSR-only island directive from the HTML
37
+ const { startOffset, endOffset } = location;
38
+ stringBuilder.overwrite(startOffset, endOffset, `<${tagName}></${tagName}>`);
39
+ }
40
+ if (!isCsr && location) {
35
41
  const { startOffset, endOffset } = location;
36
42
  const moduleSpecifier = kebabCaseToModuleSpecifier(tagName);
37
43
  ssrModules.push({
44
+ index,
38
45
  startOffset,
39
46
  endOffset,
40
47
  props,
@@ -47,27 +54,46 @@ export default function lwcSsrViewTranformer(options, { moduleBundler, resourceR
47
54
  const ssrProps = {};
48
55
  let ssrLinks = '';
49
56
  let pageTtl;
50
- await Promise.all(ssrModules.map(({ specifier, tagName, props, startOffset, endOffset }) => {
51
- return ssrElement({ specifier, props }, moduleBundler, resourceRegistry, viewContext)
57
+ await Promise.all(ssrModules.map(({ index, specifier, tagName, props: rawProps = {}, startOffset, endOffset }) => {
58
+ const hydrate = isHydrateOnLoad(rawProps);
59
+ const passProps = { ...rawProps };
60
+ delete passProps[HYDRATE_DIRECTIVE];
61
+ return ssrElement({ specifier, props: passProps }, moduleBundler, resourceRegistry, viewContext)
52
62
  .then(({ html, props = {}, markup: { links = [] } = { links: [] }, cache: { ttl } = {}, }) => {
53
63
  // Keep track of the shortest TTL from all getPageData hooks
54
64
  pageTtl = shortestTtl(ttl, pageTtl);
55
65
  // Add the props id to the HTML for the custom element
56
66
  // eg: <some-cmp> -> <some-cmp data-lwr-props-id="1234">
57
- const propsId = getPropsId();
58
- ssrProps[propsId] = props;
67
+ let propsAttr = '';
68
+ if (hydrate) {
69
+ // Only serialize props for custom elements that are to be hydrated
70
+ const propsId = getPropsId();
71
+ propsAttr = ` ${SSR_PROPS_ATTR}="${propsId}"`;
72
+ ssrProps[propsId] = props;
73
+ }
59
74
  const [, remain] = html.split(`<${tagName}`);
60
- html = [`<${tagName}`, ` ${SSR_PROPS_ATTR}="${propsId}"`, remain].join('');
75
+ html = [`<${tagName}`, propsAttr, remain].join('');
76
+ // Create HTML <link> strings for each item in the links array
61
77
  links.forEach(({ href, rel, as, fetchpriority }) => {
62
- // Create HTML <link> strings for each item in the links array
63
- const relStr = rel ? ` rel="${rel}"` : '', asStr = as ? ` as="${as}"` : '', fetchStr = fetchpriority ? ` fetchpriority="${fetchpriority}"` : '';
78
+ const relStr = rel ? ` rel="${rel}"` : '', asStr = as ? ` as="${as}"` : '', fetchStr = fetchpriority
79
+ ? ` fetchpriority="${fetchpriority}"`
80
+ : '';
64
81
  ssrLinks += `<link href="${href}"${relStr}${asStr}${fetchStr}>\n`;
65
82
  });
66
83
  // Overwrite the custom element with the SSRed component string
67
84
  stringBuilder.overwrite(startOffset, endOffset, html);
68
85
  })
69
86
  .catch((err) => {
70
- logger.warn(`Server-side rendering for "${specifier}" failed. Reason: `, err.stack);
87
+ // Fallback to CSR by adding lwr:hydrate="client-only" to the custom element
88
+ // This ENSURES the component's JavaScript gets sent to the client for CSRing
89
+ customElements[index].props === undefined
90
+ ? (customElements[index].props = {
91
+ [HYDRATE_DIRECTIVE]: HYDRATE_CLIENT_VALUE,
92
+ })
93
+ : // eslint-disable-next-line @typescript-eslint/ban-ts-comment
94
+ // @ts-ignore - TS thinks that props may still be undefined
95
+ (customElements[index].props[HYDRATE_DIRECTIVE] = HYDRATE_CLIENT_VALUE);
96
+ logger.warn(`Server-side rendering for "${specifier}" failed. Falling back to client-side rendering. Reason: `, err.stack);
71
97
  });
72
98
  }));
73
99
  if (Object.keys(ssrProps).length) {
@@ -85,7 +111,7 @@ export default function lwcSsrViewTranformer(options, { moduleBundler, resourceR
85
111
  logger.error('Adding links during server-side rendering failed. Could not find the </head> tag.');
86
112
  }
87
113
  }
88
- logger.verbose('lwcSsrViewTranformer response', stringBuilder);
114
+ logger.verbose('lwcSsrViewTransformer response', stringBuilder);
89
115
  return { cache: { ttl: pageTtl } };
90
116
  },
91
117
  };
@@ -1,5 +1,7 @@
1
+ import { getTracer } from '@lwrjs/instrumentation';
1
2
  import createVirtualEnvironment from '@locker/near-membrane-node';
2
3
  import fetch from 'node-fetch';
4
+ import { webcrypto as crypto } from 'crypto';
3
5
  export default function runCode(codes, context) {
4
6
  return new Promise((resolve, reject) => {
5
7
  let resolver;
@@ -7,8 +9,13 @@ export default function runCode(codes, context) {
7
9
  function getContext() {
8
10
  return context;
9
11
  }
12
+ const tracer = getTracer();
13
+ const trace = tracer.trace.bind(tracer);
10
14
  const endowments = Object.getOwnPropertyDescriptors({
15
+ AbortController,
16
+ crypto,
11
17
  getContext,
18
+ trace,
12
19
  fetch,
13
20
  resolver,
14
21
  // for AMD loader ModuleRegistry
@@ -15,7 +15,10 @@ const WORKER_CODE_SANDBOX_APIS = [
15
15
  `const { parentPort, workerData } = require('worker_threads');`,
16
16
  `globalThis.getContext = () => workerData;`,
17
17
  `globalThis.fetch = require('node-fetch');`,
18
+ `globalThis.crypto = require('crypto').webcrypto;`,
18
19
  `globalThis.resolver = (...args) => parentPort.postMessage(...args);`,
20
+ // TODO: implement tracing with worker thread
21
+ `globalThis.trace = (id, fn) => fn()`,
19
22
  ];
20
23
  /**
21
24
  * Run the SSR module code in a worker, and return the results to the main thread.
@@ -1,4 +1,4 @@
1
- import { PageDataResponse, SsrRequestContext } from '@lwrjs/types';
1
+ import type { PageDataResponse, SsrRequestContext } from '@lwrjs/types';
2
2
  export interface SandboxResults extends PageDataResponse {
3
3
  result?: string;
4
4
  error?: string;
@@ -1,4 +1,4 @@
1
- import type { Json, PageDataResponse, PublicModuleBundler, PublicResourceRegistry, ViewTranformPluginContext } from '@lwrjs/types';
1
+ import type { Json, PageDataResponse, PublicModuleBundler, PublicResourceRegistry, ViewTransformPluginContext } 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: PublicModuleBundler, resourceRegistry: PublicResourceRegistry, { runtimeEnvironment, runtimeParams }: ViewTranformPluginContext): Promise<SsrResults>;
16
+ }, moduleBundler: PublicModuleBundler, resourceRegistry: PublicResourceRegistry, { runtimeEnvironment, runtimeParams }: ViewTransformPluginContext): Promise<SsrResults>;
17
17
  export {};
18
18
  //# sourceMappingURL=ssr-element.d.ts.map
@@ -2,6 +2,8 @@ import { getBundle, getCode } from './amd-utils.js';
2
2
  import runCode from './sandbox.js';
3
3
  import { performance } from 'perf_hooks';
4
4
  import { logger } from '@lwrjs/shared-utils';
5
+ import { getTracer, ViewSpan } from '@lwrjs/instrumentation';
6
+ const SSR_FLAG = 'process.env.SSR = true;';
5
7
  /**
6
8
  * Create a bundle for the given SSR module and run the code in a sandbox.
7
9
  * @param moduleInfo - specifier: The ID of the module, generated by "lwc-ssr/moduleProvider", which SSRs a component
@@ -34,17 +36,25 @@ export async function ssrElement({ specifier, props: templateProps }, moduleBund
34
36
  };
35
37
  // Get the SSR string and properties bag
36
38
  const startTime = performance.now();
37
- const { result, props, markup, cache, error } = format === 'amd'
38
- ? await runCode([
39
- ...(await getCode(runtimeEnvironment, version.replace(/\./g, '_'), bundleSpecifier, bundleRecord.includedModules, resourceRegistry)),
40
- code,
41
- ], context)
42
- : await runCode([code], context);
43
- const endTime = performance.now();
44
- const timeDiff = endTime - startTime;
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 };
39
+ return getTracer().trace({
40
+ name: ViewSpan.ServerSideRender,
41
+ attributes: {
42
+ specifier: bundleSpecifier,
43
+ },
44
+ }, async () => {
45
+ const { result, props, markup, cache, error } = format === 'amd'
46
+ ? await runCode([
47
+ SSR_FLAG,
48
+ ...(await getCode(runtimeEnvironment, version.replace(/\./g, '_'), bundleSpecifier, bundleRecord.includedModules, resourceRegistry)),
49
+ code,
50
+ ], context)
51
+ : await runCode([SSR_FLAG, code], context);
52
+ const endTime = performance.now();
53
+ const timeDiff = endTime - startTime;
54
+ logger.info(`[Server-side Rendering] ${specifier} in ${Math.round(timeDiff)} ms`);
55
+ if (error)
56
+ throw new Error(error);
57
+ return { html: result, props, markup, cache };
58
+ });
49
59
  }
50
60
  //# sourceMappingURL=ssr-element.js.map
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.10.0-alpha.8",
7
+ "version": "0.10.0",
8
8
  "homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
9
9
  "repository": {
10
10
  "type": "git",
@@ -33,16 +33,17 @@
33
33
  "build/**/*.d.ts"
34
34
  ],
35
35
  "dependencies": {
36
- "@locker/near-membrane-node": "^0.12.0",
37
- "@lwrjs/diagnostics": "0.10.0-alpha.8",
38
- "@lwrjs/shared-utils": "0.10.0-alpha.8",
36
+ "@locker/near-membrane-node": "^0.12.15",
37
+ "@lwrjs/diagnostics": "0.10.0",
38
+ "@lwrjs/instrumentation": "0.10.0",
39
+ "@lwrjs/shared-utils": "0.10.0",
39
40
  "node-fetch": "^2.6.8"
40
41
  },
41
42
  "devDependencies": {
42
- "@lwrjs/types": "0.10.0-alpha.8"
43
+ "@lwrjs/types": "0.10.0"
43
44
  },
44
45
  "engines": {
45
- "node": ">=16.0.0 <20"
46
+ "node": ">=16.0.0"
46
47
  },
47
- "gitHead": "46bd72343e1db7c72470033320921df3a7eff18a"
48
+ "gitHead": "e6deaeef3db8aa079acefed508897eca19b3218a"
48
49
  }