@lwrjs/lwc-ssr 0.7.0-alpha.1 → 0.7.0-alpha.12
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 +127 -0
- package/build/cjs/identity.cjs +9 -1
- package/build/cjs/moduleProvider/index.cjs +32 -32
- package/build/cjs/viewTransformer/amd-utils.cjs +88 -0
- package/build/cjs/viewTransformer/index.cjs +32 -11
- package/build/cjs/viewTransformer/ssr-element.cjs +28 -17
- package/build/es/identity.d.ts +3 -0
- package/build/es/identity.js +5 -0
- package/build/es/moduleProvider/index.d.ts +17 -5
- package/build/es/moduleProvider/index.js +45 -30
- package/build/es/viewTransformer/amd-utils.d.ts +3 -0
- package/build/es/viewTransformer/amd-utils.js +70 -0
- package/build/es/viewTransformer/index.d.ts +20 -2
- package/build/es/viewTransformer/index.js +61 -15
- package/build/es/viewTransformer/ssr-element.d.ts +16 -2
- package/build/es/viewTransformer/ssr-element.js +43 -16
- package/package.json +5 -5
- package/build/cjs/moduleProvider/utils.cjs +0 -42
- package/build/es/moduleProvider/utils.d.ts +0 -6
- package/build/es/moduleProvider/utils.js +0 -19
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Server-side rendering (SSR) in LWR
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
### What is SSR?
|
|
6
|
+
|
|
7
|
+
[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
|
+
|
|
9
|
+
### Why use SSR?
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
That said, SSR is best used for apps where time-to-content is important, such as B2C websites. The benefits of SSR should be weighed against the costs: higher server load, increased build & deployment complexity, developing server-compatible component code.
|
|
14
|
+
|
|
15
|
+
## Using SSR with LWR
|
|
16
|
+
|
|
17
|
+
Learn how to use SSR in your LWR apps.
|
|
18
|
+
|
|
19
|
+
### Turn on SSR
|
|
20
|
+
|
|
21
|
+
SSR is activated on a per-route basis by changing `bootstrap.experimentalSSR` to `true`:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
// my-app/lwr.config.json
|
|
25
|
+
{
|
|
26
|
+
"routes": [
|
|
27
|
+
{
|
|
28
|
+
"id": "ssr-page",
|
|
29
|
+
// parameterized path
|
|
30
|
+
"path": "/category/:category",
|
|
31
|
+
// content template
|
|
32
|
+
"contentTemplate": "$contentDir/page.html",
|
|
33
|
+
"bootstrap": {
|
|
34
|
+
// turn on SSR for the page here
|
|
35
|
+
"experimentalSSR": true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Building SSR pages
|
|
43
|
+
|
|
44
|
+
When a route with `experimentalSSR` 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
|
+
|
|
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).
|
|
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' }`.
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<!-- my-app/src/content/page.html -->
|
|
52
|
+
<section>
|
|
53
|
+
<!-- "limit" is a template attribute property -->
|
|
54
|
+
<my-root limit="10"></my-root>
|
|
55
|
+
</section>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Limitations
|
|
59
|
+
|
|
60
|
+
There are restrictions on component code for it to successfully render on the server. The `renderComponent()` function executes the [`constructor` and `connectedCallback`](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/reference_lifecycle_hooks) of each component. These functions must be free of browser-specific code, such as DOM manipulation, eventing, and fetching data.
|
|
61
|
+
|
|
62
|
+
Because of this, the [`@lwrjs/router`](https://github.com/salesforce/lwr-recipes/blob/main/doc/navigation.md) is not supported with SSR.
|
|
63
|
+
|
|
64
|
+
### Preloading data during SSR
|
|
65
|
+
|
|
66
|
+
Many components depend on external data. LWR provides a `getProps()` hook for developers to fetch that data on the server. LWR passes the data to the component during SSR as [properties](<(https://developer.salesforce.com/docs/component-library/documentation/en/lwc/reactivity_public)>).
|
|
67
|
+
|
|
68
|
+
> **Important**: This hook is **only** run for root components.
|
|
69
|
+
|
|
70
|
+
The `getProps()` hook is exported as a function from a root component module:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// my-app/src/modules/my/root/root.ts
|
|
74
|
+
import { LightningElement, api } from 'lwc';
|
|
75
|
+
import type { PropsRequestContext, PropsResponse } from '@lwrjs/types';
|
|
76
|
+
|
|
77
|
+
export default class MyRoot extends LightningElement {
|
|
78
|
+
@api data: SomeDataType[] = [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function getProps(context: PropsRequestContext): Promise<PropsResponse> {
|
|
82
|
+
// "/category/books" => context.params = { category: 'books' }
|
|
83
|
+
const category = context.params.category;
|
|
84
|
+
// page.html template => context.props = { limit: '10' }
|
|
85
|
+
const num = context.props.limit || '25';
|
|
86
|
+
const res = await context.fetch(`https://www.some-api.com/${category}?lang=${context.locale}&num=${num}`);
|
|
87
|
+
const data = await res.json();
|
|
88
|
+
return {
|
|
89
|
+
props: {
|
|
90
|
+
// will be passed to the root component as props
|
|
91
|
+
data,
|
|
92
|
+
...context.props, // pass the template props through, if desired
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
type GetPropsHook = (context: PropsRequestContext) => Promise<PropsResponse>;
|
|
100
|
+
|
|
101
|
+
interface PropsRequestContext {
|
|
102
|
+
// existing props from template attributes
|
|
103
|
+
props: Json;
|
|
104
|
+
// values from a parameterized route defined in lwr.config.json
|
|
105
|
+
params: { [key: string]: string };
|
|
106
|
+
// locale string for the request, eg: 'en-US'
|
|
107
|
+
locale: string;
|
|
108
|
+
// server-friendly fetch() function for accessing data
|
|
109
|
+
fetch: Function;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface PropsResponse {
|
|
113
|
+
// serializable props for the root component
|
|
114
|
+
props: Json;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
type Json = undefined | null | boolean | number | string | Json[] | { [prop: string]: Json };
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Notes:
|
|
121
|
+
|
|
122
|
+
- The `getProps()` hook can choose to merge the properties from `PropsRequestContext.props` into its return object, or it can ignore/discard them.
|
|
123
|
+
- The **same** `props` returned by `getProps()` are passed to the component during server rendering **and** client hydration.
|
|
124
|
+
|
|
125
|
+
### Client hydration
|
|
126
|
+
|
|
127
|
+
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.
|
package/build/cjs/identity.cjs
CHANGED
|
@@ -8,6 +8,14 @@ var __export = (target, all) => {
|
|
|
8
8
|
// packages/@lwrjs/lwc-ssr/src/identity.ts
|
|
9
9
|
__markAsModule(exports);
|
|
10
10
|
__export(exports, {
|
|
11
|
-
LWC_SSR_PREFIX: () => LWC_SSR_PREFIX
|
|
11
|
+
LWC_SSR_PREFIX: () => LWC_SSR_PREFIX,
|
|
12
|
+
SSR_PROPS_ATTR: () => SSR_PROPS_ATTR,
|
|
13
|
+
SSR_PROPS_KEY: () => SSR_PROPS_KEY,
|
|
14
|
+
getPropsId: () => getPropsId
|
|
12
15
|
});
|
|
13
16
|
var LWC_SSR_PREFIX = "@lwrjs/lwc-ssr/";
|
|
17
|
+
var SSR_PROPS_ATTR = "data-lwr-props-id";
|
|
18
|
+
var SSR_PROPS_KEY = "ssrProps";
|
|
19
|
+
function getPropsId() {
|
|
20
|
+
return `lwcprops${Math.floor(Math.random() * 65536).toString(16)}`;
|
|
21
|
+
}
|
|
@@ -24,20 +24,40 @@ var __toModule = (module2) => {
|
|
|
24
24
|
// packages/@lwrjs/lwc-ssr/src/moduleProvider/index.ts
|
|
25
25
|
__markAsModule(exports);
|
|
26
26
|
__export(exports, {
|
|
27
|
+
createSsrBootstrapModule: () => createSsrBootstrapModule,
|
|
27
28
|
default: () => moduleProvider_default
|
|
28
29
|
});
|
|
29
|
-
var import_diagnostics = __toModule(require("@lwrjs/diagnostics"));
|
|
30
30
|
var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
|
|
31
|
-
var import_utils = __toModule(require("./utils.cjs"));
|
|
32
31
|
var import_identity = __toModule(require("../identity.cjs"));
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
function createSsrBootstrapModule(rootSpecifier) {
|
|
33
|
+
return `/* This module is generated and meant to be used in a Server context */
|
|
34
|
+
const { parentPort, workerData } = require('worker_threads');
|
|
35
|
+
const fetch = require('node-fetch');
|
|
36
|
+
import { renderComponent } from '@lwc/engine-server';
|
|
37
|
+
import Ctor, * as rootComponent from '${rootSpecifier}';
|
|
38
|
+
|
|
39
|
+
(async () => {
|
|
40
|
+
try {
|
|
41
|
+
let props = workerData.props;
|
|
42
|
+
if (rootComponent.getProps) {
|
|
43
|
+
const data = await rootComponent.getProps({
|
|
44
|
+
props,
|
|
45
|
+
params: workerData.params,
|
|
46
|
+
locale: workerData.locale,
|
|
47
|
+
fetch,
|
|
48
|
+
});
|
|
49
|
+
props = data.props; // overwrite public props
|
|
50
|
+
}
|
|
51
|
+
const result = renderComponent('${(0, import_shared_utils.moduleSpecifierToKebabCase)(rootSpecifier)}', Ctor, props || {});
|
|
52
|
+
parentPort.postMessage({ result, props });
|
|
53
|
+
} catch(e) {
|
|
54
|
+
parentPort.postMessage({ error: e });
|
|
55
|
+
}
|
|
56
|
+
})()`;
|
|
36
57
|
}
|
|
37
58
|
var LwcSsrModuleProvider = class {
|
|
38
|
-
constructor(
|
|
59
|
+
constructor(providerConfig, {runtimeEnvironment: {lwrVersion}}) {
|
|
39
60
|
this.name = "ssr-module-provider";
|
|
40
|
-
this.config = config;
|
|
41
61
|
this.version = lwrVersion;
|
|
42
62
|
}
|
|
43
63
|
async getModuleEntry({specifier}) {
|
|
@@ -52,22 +72,12 @@ var LwcSsrModuleProvider = class {
|
|
|
52
72
|
};
|
|
53
73
|
}
|
|
54
74
|
}
|
|
55
|
-
async
|
|
56
|
-
specifier,
|
|
57
|
-
namespace,
|
|
58
|
-
name
|
|
59
|
-
}) {
|
|
60
|
-
if (!specifier.startsWith(import_identity.LWC_SSR_PREFIX)) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
75
|
+
async getModule({specifier, namespace, name}) {
|
|
63
76
|
const moduleEntry = await this.getModuleEntry({specifier});
|
|
64
77
|
if (!moduleEntry) {
|
|
65
|
-
|
|
66
|
-
description: import_diagnostics.descriptions.UNRESOLVABLE.MODULE(specifier)
|
|
67
|
-
}, import_diagnostics.LwrUnresolvableError);
|
|
78
|
+
return;
|
|
68
79
|
}
|
|
69
|
-
const
|
|
70
|
-
const originalSource = (0, import_utils.createSsrBootstrapModule)(customElementModule);
|
|
80
|
+
const originalSource = createSsrBootstrapModule(specifier.replace(import_identity.LWC_SSR_PREFIX, ""));
|
|
71
81
|
return {
|
|
72
82
|
id: moduleEntry.id,
|
|
73
83
|
namespace,
|
|
@@ -76,18 +86,8 @@ var LwcSsrModuleProvider = class {
|
|
|
76
86
|
specifier,
|
|
77
87
|
moduleEntry,
|
|
78
88
|
ownHash: (0, import_shared_utils.hashContent)(originalSource),
|
|
79
|
-
originalSource
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
async getModule(moduleId) {
|
|
83
|
-
if (!moduleId.specifier.startsWith("@lwrjs/lwc-ssr/")) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
const moduleSource = await this.getModuleSource(moduleId);
|
|
87
|
-
const compiledSource = moduleSource.originalSource;
|
|
88
|
-
return {
|
|
89
|
-
...moduleSource,
|
|
90
|
-
compiledSource
|
|
89
|
+
originalSource,
|
|
90
|
+
compiledSource: originalSource
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
93
|
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, {get: all[name], enumerable: true});
|
|
11
|
+
};
|
|
12
|
+
var __exportStar = (target, module2, desc) => {
|
|
13
|
+
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(module2))
|
|
15
|
+
if (!__hasOwnProp.call(target, key) && key !== "default")
|
|
16
|
+
__defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
|
|
17
|
+
}
|
|
18
|
+
return target;
|
|
19
|
+
};
|
|
20
|
+
var __toModule = (module2) => {
|
|
21
|
+
return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// packages/@lwrjs/lwc-ssr/src/viewTransformer/amd-utils.ts
|
|
25
|
+
__markAsModule(exports);
|
|
26
|
+
__export(exports, {
|
|
27
|
+
default: () => getCode
|
|
28
|
+
});
|
|
29
|
+
var import_loader = __toModule(require("@lwrjs/loader"));
|
|
30
|
+
var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
|
|
31
|
+
async function readableToString(readable) {
|
|
32
|
+
let result = "";
|
|
33
|
+
for await (const chunk of readable) {
|
|
34
|
+
result += chunk;
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
var loaderShimSource;
|
|
39
|
+
async function getLoaderShim(runtimeEnvironment) {
|
|
40
|
+
if (loaderShimSource !== void 0) {
|
|
41
|
+
return loaderShimSource;
|
|
42
|
+
}
|
|
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);
|
|
49
|
+
if (resource) {
|
|
50
|
+
const {stream} = resource;
|
|
51
|
+
if (stream) {
|
|
52
|
+
loaderShimSource = await readableToString(stream);
|
|
53
|
+
return loaderShimSource;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function getLwrConfig(bundleSpecifier, lwrVersion) {
|
|
58
|
+
return Object.assign({}, {
|
|
59
|
+
autoBoot: true,
|
|
60
|
+
bootstrapModule: `${bundleSpecifier}/v/${lwrVersion}`,
|
|
61
|
+
disableInitDefer: true,
|
|
62
|
+
endpoints: {
|
|
63
|
+
uris: {mapping: "/1/mapping/amd/1/l/en-US/mp/"}
|
|
64
|
+
},
|
|
65
|
+
rootComponents: [`${bundleSpecifier.split("@lwrjs/lwc-ssr/")[1]}/v/${lwrVersion}`]
|
|
66
|
+
}, (0, import_shared_utils.getFeatureFlags)().LEGACY_LOADER ? {
|
|
67
|
+
baseUrl: "ssr"
|
|
68
|
+
} : {
|
|
69
|
+
baseUrl: "/",
|
|
70
|
+
imports: {
|
|
71
|
+
"any/thing.js": ["any/thing"]
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function lwcDefineOverride(lwcSpecifier) {
|
|
76
|
+
return `LWR.define("${lwcSpecifier}", ["${lwcSpecifier.replace("lwc", "@lwc/engine-server")}"], function(lwcEngine) { return lwcEngine; });`;
|
|
77
|
+
}
|
|
78
|
+
var GLOBALTHIS_LWR = `globalThis.LWR = globalThis.LWR || {};`;
|
|
79
|
+
async function getCode(runtimeEnvironment, lwrVersion, lwcSpecifier, bundleSpecifier) {
|
|
80
|
+
const loaderShimSource2 = await getLoaderShim(runtimeEnvironment);
|
|
81
|
+
const lwrConfigString = JSON.stringify(getLwrConfig(bundleSpecifier, lwrVersion));
|
|
82
|
+
return [
|
|
83
|
+
GLOBALTHIS_LWR,
|
|
84
|
+
`Object.assign(globalThis.LWR, ${lwrConfigString});`,
|
|
85
|
+
loaderShimSource2 ? loaderShimSource2 : "",
|
|
86
|
+
lwcSpecifier ? lwcDefineOverride(lwcSpecifier) : ""
|
|
87
|
+
];
|
|
88
|
+
}
|
|
@@ -29,22 +29,43 @@ __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,
|
|
33
|
-
const {moduleBundler} = lwrGlobalContext;
|
|
32
|
+
function lwcSsrViewTranformer(options, {moduleBundler}) {
|
|
34
33
|
return {
|
|
35
34
|
name: "ssr-lwc-transformer",
|
|
36
|
-
async link(stringBuilder, viewContext,
|
|
35
|
+
async link(stringBuilder, viewContext, {customElements}) {
|
|
36
|
+
if (process.env.LOCKER === "true") {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
37
39
|
if (viewContext.view.bootstrap?.experimentalSSR) {
|
|
38
|
-
const
|
|
39
|
-
for (const
|
|
40
|
-
if (
|
|
41
|
-
const {startOffset, endOffset} =
|
|
42
|
-
const moduleSpecifier = (0, import_shared_utils.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
const ssrModules = [];
|
|
41
|
+
for (const {tagName, location, props} of customElements) {
|
|
42
|
+
if (location) {
|
|
43
|
+
const {startOffset, endOffset} = location;
|
|
44
|
+
const moduleSpecifier = (0, import_shared_utils.kebabCaseToModuleSpecifer)(tagName);
|
|
45
|
+
ssrModules.push({
|
|
46
|
+
startOffset,
|
|
47
|
+
endOffset,
|
|
48
|
+
props,
|
|
49
|
+
tagName,
|
|
50
|
+
specifier: `${import_identity.LWC_SSR_PREFIX}${moduleSpecifier}`
|
|
51
|
+
});
|
|
46
52
|
}
|
|
47
53
|
}
|
|
54
|
+
const ssrProps = {};
|
|
55
|
+
await Promise.all(ssrModules.map(({specifier, tagName, props, startOffset, endOffset}) => {
|
|
56
|
+
return (0, import_ssr_element.ssrElement)({specifier, props}, moduleBundler, viewContext).then(({html, props: props2}) => {
|
|
57
|
+
if (props2) {
|
|
58
|
+
const propsId = (0, import_identity.getPropsId)();
|
|
59
|
+
ssrProps[propsId] = props2;
|
|
60
|
+
const [, remain] = html.split(`<${tagName}`);
|
|
61
|
+
html = [`<${tagName}`, ` ${import_identity.SSR_PROPS_ATTR}="${propsId}"`, remain].join("");
|
|
62
|
+
}
|
|
63
|
+
stringBuilder.overwrite(startOffset, endOffset, html);
|
|
64
|
+
});
|
|
65
|
+
}));
|
|
66
|
+
if (Object.keys(ssrProps).length) {
|
|
67
|
+
stringBuilder.prependLeft(ssrModules[0].startOffset, `<script type="application/javascript">globalThis.LWR = globalThis.LWR || {};globalThis.LWR.${import_identity.SSR_PROPS_KEY} = ${JSON.stringify(ssrProps)};</script>`);
|
|
68
|
+
}
|
|
48
69
|
}
|
|
49
70
|
}
|
|
50
71
|
};
|
|
@@ -27,34 +27,45 @@ __export(exports, {
|
|
|
27
27
|
ssrElement: () => ssrElement
|
|
28
28
|
});
|
|
29
29
|
var import_worker_threads = __toModule(require("worker_threads"));
|
|
30
|
+
var import_amd_utils = __toModule(require("./amd-utils.cjs"));
|
|
30
31
|
var bundleConfigOverrides = {
|
|
31
32
|
exclude: [],
|
|
32
33
|
alias: {
|
|
33
34
|
lwc: "@lwc/engine-server"
|
|
34
35
|
}
|
|
35
36
|
};
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return new Promise((resolve, reject) => {
|
|
41
|
-
const worker = new import_worker_threads.Worker(workerCode, {eval: true});
|
|
37
|
+
function runCodeOnWorker(codes, workerData) {
|
|
38
|
+
const workerCode = codes.join("\n");
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const worker = new import_worker_threads.Worker(workerCode, {eval: true, workerData});
|
|
42
41
|
worker.on("message", resolve);
|
|
43
|
-
worker.on("error",
|
|
44
|
-
worker.on("exit", (
|
|
45
|
-
if (
|
|
46
|
-
|
|
42
|
+
worker.on("error", ({message}) => resolve({error: `SSR worker exited with error: ${message}`}));
|
|
43
|
+
worker.on("exit", (code) => {
|
|
44
|
+
if (code !== 0) {
|
|
45
|
+
resolve({error: `SSR worker stopped with exit code: ${code}`});
|
|
47
46
|
}
|
|
48
47
|
});
|
|
49
48
|
});
|
|
50
49
|
}
|
|
51
|
-
async function ssrElement(
|
|
52
|
-
const {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
async function ssrElement({specifier, props: templateProps}, moduleBundler, {runtimeEnvironment, runtimeParams}) {
|
|
51
|
+
const {
|
|
52
|
+
bundleRecord,
|
|
53
|
+
code,
|
|
54
|
+
specifier: bundleSpecifier,
|
|
55
|
+
version
|
|
56
|
+
} = await moduleBundler.getModuleBundle({specifier}, {...runtimeEnvironment, bundle: false}, void 0, bundleConfigOverrides);
|
|
57
|
+
const context = {
|
|
58
|
+
props: templateProps,
|
|
59
|
+
params: runtimeParams.params || {},
|
|
60
|
+
locale: runtimeParams.locale || runtimeEnvironment.defaultLocale
|
|
61
|
+
};
|
|
62
|
+
const {error, result, props} = runtimeEnvironment.format === "amd" ? await runCodeOnWorker([
|
|
63
|
+
...await (0, import_amd_utils.default)(runtimeEnvironment, version.replace(/\./g, "_"), bundleRecord.includedModules.find((m) => m.startsWith("lwc/v")), bundleSpecifier),
|
|
64
|
+
code
|
|
65
|
+
], context) : await runCodeOnWorker([code], context);
|
|
66
|
+
if (error) {
|
|
67
|
+
throw new Error(error);
|
|
57
68
|
} else {
|
|
58
|
-
return result
|
|
69
|
+
return {html: result, props};
|
|
59
70
|
}
|
|
60
71
|
}
|
package/build/es/identity.d.ts
CHANGED
package/build/es/identity.js
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
export const LWC_SSR_PREFIX = '@lwrjs/lwc-ssr/';
|
|
2
|
+
export const SSR_PROPS_ATTR = 'data-lwr-props-id';
|
|
3
|
+
export const SSR_PROPS_KEY = 'ssrProps';
|
|
4
|
+
export function getPropsId() {
|
|
5
|
+
return `lwcprops${Math.floor(Math.random() * 0x10000).toString(16)}`;
|
|
6
|
+
}
|
|
2
7
|
//# sourceMappingURL=identity.js.map
|
|
@@ -1,11 +1,23 @@
|
|
|
1
|
-
import { ModuleCompiled, ModuleEntry, ModuleProvider,
|
|
1
|
+
import { ModuleCompiled, ModuleEntry, ModuleProvider, ProviderContext, AbstractModuleId } from '@lwrjs/types';
|
|
2
|
+
/**
|
|
3
|
+
* This module provider generates code which server-side renders a given component.
|
|
4
|
+
* It handles module specifiers in the form: "@lwrjs/lwc-ssr/${component specifier}".
|
|
5
|
+
* eg: "@lwrjs/lwc-ssr/my/appRoot"
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Create the virtual source for a module which server-side renders the given component.
|
|
9
|
+
* This code is meant to be executed in a worker on the server; it is run from "lwc-ssr/viewTransformer#ssr-element" during linking.
|
|
10
|
+
* The result is posted to the parentPort and the Promise in the main thread resolves.
|
|
11
|
+
* If available, getProps() is called on the root component to mutate workerData.props
|
|
12
|
+
* @param rootSpecifier - The specifier for the component to SSR
|
|
13
|
+
* @returns the generated module source
|
|
14
|
+
*/
|
|
15
|
+
export declare function createSsrBootstrapModule(rootSpecifier: string): string;
|
|
2
16
|
export default class LwcSsrModuleProvider implements ModuleProvider {
|
|
3
17
|
name: string;
|
|
4
18
|
version: string;
|
|
5
|
-
|
|
6
|
-
constructor(appPluginConfig: unknown, { config, runtimeEnvironment: { hmrEnabled, lwrVersion } }: ProviderContext);
|
|
19
|
+
constructor(providerConfig: unknown, { runtimeEnvironment: { lwrVersion } }: ProviderContext);
|
|
7
20
|
getModuleEntry({ specifier }: AbstractModuleId): Promise<ModuleEntry | undefined>;
|
|
8
|
-
|
|
9
|
-
getModule(moduleId: AbstractModuleId): Promise<ModuleCompiled | undefined>;
|
|
21
|
+
getModule({ specifier, namespace, name }: AbstractModuleId): Promise<ModuleCompiled | undefined>;
|
|
10
22
|
}
|
|
11
23
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,15 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { hashContent } from '@lwrjs/shared-utils';
|
|
3
|
-
import { createSsrBootstrapModule } from './utils.js';
|
|
1
|
+
import { hashContent, moduleSpecifierToKebabCase } from '@lwrjs/shared-utils';
|
|
4
2
|
import { LWC_SSR_PREFIX } from '../identity.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
/**
|
|
4
|
+
* This module provider generates code which server-side renders a given component.
|
|
5
|
+
* It handles module specifiers in the form: "@lwrjs/lwc-ssr/${component specifier}".
|
|
6
|
+
* eg: "@lwrjs/lwc-ssr/my/appRoot"
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Create the virtual source for a module which server-side renders the given component.
|
|
10
|
+
* This code is meant to be executed in a worker on the server; it is run from "lwc-ssr/viewTransformer#ssr-element" during linking.
|
|
11
|
+
* The result is posted to the parentPort and the Promise in the main thread resolves.
|
|
12
|
+
* If available, getProps() is called on the root component to mutate workerData.props
|
|
13
|
+
* @param rootSpecifier - The specifier for the component to SSR
|
|
14
|
+
* @returns the generated module source
|
|
15
|
+
*/
|
|
16
|
+
export function createSsrBootstrapModule(rootSpecifier) {
|
|
17
|
+
return `/* This module is generated and meant to be used in a Server context */
|
|
18
|
+
const { parentPort, workerData } = require('worker_threads');
|
|
19
|
+
const fetch = require('node-fetch');
|
|
20
|
+
import { renderComponent } from '@lwc/engine-server';
|
|
21
|
+
import Ctor, * as rootComponent from '${rootSpecifier}';
|
|
22
|
+
|
|
23
|
+
(async () => {
|
|
24
|
+
try {
|
|
25
|
+
let props = workerData.props;
|
|
26
|
+
if (rootComponent.getProps) {
|
|
27
|
+
const data = await rootComponent.getProps({
|
|
28
|
+
props,
|
|
29
|
+
params: workerData.params,
|
|
30
|
+
locale: workerData.locale,
|
|
31
|
+
fetch,
|
|
32
|
+
});
|
|
33
|
+
props = data.props; // overwrite public props
|
|
34
|
+
}
|
|
35
|
+
const result = renderComponent('${moduleSpecifierToKebabCase(rootSpecifier)}', Ctor, props || {});
|
|
36
|
+
parentPort.postMessage({ result, props });
|
|
37
|
+
} catch(e) {
|
|
38
|
+
parentPort.postMessage({ error: e });
|
|
39
|
+
}
|
|
40
|
+
})()`;
|
|
8
41
|
}
|
|
9
42
|
export default class LwcSsrModuleProvider {
|
|
10
|
-
constructor(
|
|
43
|
+
constructor(providerConfig, { runtimeEnvironment: { lwrVersion } }) {
|
|
11
44
|
this.name = 'ssr-module-provider';
|
|
12
|
-
this.config = config;
|
|
13
45
|
this.version = lwrVersion;
|
|
14
46
|
}
|
|
15
47
|
async getModuleEntry({ specifier }) {
|
|
@@ -24,19 +56,13 @@ export default class LwcSsrModuleProvider {
|
|
|
24
56
|
};
|
|
25
57
|
}
|
|
26
58
|
}
|
|
27
|
-
async
|
|
28
|
-
if (!specifier.startsWith(LWC_SSR_PREFIX)) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
// Fetch the Module Entry and corresponding route config object
|
|
59
|
+
async getModule({ specifier, namespace, name }) {
|
|
32
60
|
const moduleEntry = await this.getModuleEntry({ specifier });
|
|
33
61
|
if (!moduleEntry) {
|
|
34
|
-
|
|
35
|
-
description: descriptions.UNRESOLVABLE.MODULE(specifier),
|
|
36
|
-
}, LwrUnresolvableError);
|
|
62
|
+
return;
|
|
37
63
|
}
|
|
38
|
-
|
|
39
|
-
const originalSource = createSsrBootstrapModule(
|
|
64
|
+
// Generate the module source which SSRs a lwc, and return the Module
|
|
65
|
+
const originalSource = createSsrBootstrapModule(specifier.replace(LWC_SSR_PREFIX, ''));
|
|
40
66
|
return {
|
|
41
67
|
id: moduleEntry.id,
|
|
42
68
|
namespace,
|
|
@@ -46,18 +72,7 @@ export default class LwcSsrModuleProvider {
|
|
|
46
72
|
moduleEntry,
|
|
47
73
|
ownHash: hashContent(originalSource),
|
|
48
74
|
originalSource,
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
async getModule(moduleId) {
|
|
52
|
-
if (!moduleId.specifier.startsWith('@lwrjs/lwc-ssr/')) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
// Get the source to compile
|
|
56
|
-
const moduleSource = (await this.getModuleSource(moduleId));
|
|
57
|
-
const compiledSource = moduleSource.originalSource;
|
|
58
|
-
return {
|
|
59
|
-
...moduleSource,
|
|
60
|
-
compiledSource,
|
|
75
|
+
compiledSource: originalSource,
|
|
61
76
|
};
|
|
62
77
|
}
|
|
63
78
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import AmdLoaderShimService from '@lwrjs/loader';
|
|
2
|
+
import { getFeatureFlags } from '@lwrjs/shared-utils';
|
|
3
|
+
async function readableToString(readable) {
|
|
4
|
+
let result = '';
|
|
5
|
+
for await (const chunk of readable) {
|
|
6
|
+
result += chunk;
|
|
7
|
+
}
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
let loaderShimSource;
|
|
11
|
+
async function getLoaderShim(runtimeEnvironment) {
|
|
12
|
+
if (loaderShimSource !== undefined) {
|
|
13
|
+
return loaderShimSource;
|
|
14
|
+
}
|
|
15
|
+
const amdloaderShimService = new AmdLoaderShimService({}, {
|
|
16
|
+
runtimeEnvironment,
|
|
17
|
+
resourceRegistry: { resolveResourceUri: () => 'NOT IMPLEMENTED' },
|
|
18
|
+
});
|
|
19
|
+
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);
|
|
23
|
+
if (resource) {
|
|
24
|
+
const { stream } = resource;
|
|
25
|
+
if (stream) {
|
|
26
|
+
loaderShimSource = await readableToString(stream);
|
|
27
|
+
return loaderShimSource;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function getLwrConfig(bundleSpecifier, lwrVersion) {
|
|
32
|
+
// This is the minimum LWR config required for the loader to be created and mountApp to succeed
|
|
33
|
+
return Object.assign({}, {
|
|
34
|
+
autoBoot: true,
|
|
35
|
+
bootstrapModule: `${bundleSpecifier}/v/${lwrVersion}`,
|
|
36
|
+
disableInitDefer: true,
|
|
37
|
+
endpoints: {
|
|
38
|
+
uris: { mapping: '/1/mapping/amd/1/l/en-US/mp/' },
|
|
39
|
+
},
|
|
40
|
+
rootComponents: [`${bundleSpecifier.split('@lwrjs/lwc-ssr/')[1]}/v/${lwrVersion}`],
|
|
41
|
+
}, getFeatureFlags().LEGACY_LOADER
|
|
42
|
+
? {
|
|
43
|
+
baseUrl: 'ssr',
|
|
44
|
+
}
|
|
45
|
+
: {
|
|
46
|
+
baseUrl: '/',
|
|
47
|
+
imports: {
|
|
48
|
+
'any/thing.js': ['any/thing'],
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// lwc/v/2_13_0 -> @lwc/engine-server/v/2_13_0
|
|
53
|
+
function lwcDefineOverride(lwcSpecifier) {
|
|
54
|
+
return `LWR.define("${lwcSpecifier}", ["${lwcSpecifier.replace('lwc', '@lwc/engine-server')}"], function(lwcEngine) { return lwcEngine; });`;
|
|
55
|
+
}
|
|
56
|
+
const GLOBALTHIS_LWR = `globalThis.LWR = globalThis.LWR || {};`;
|
|
57
|
+
export default async function getCode(runtimeEnvironment, lwrVersion, lwcSpecifier, bundleSpecifier) {
|
|
58
|
+
const loaderShimSource = await getLoaderShim(runtimeEnvironment);
|
|
59
|
+
const lwrConfigString = JSON.stringify(getLwrConfig(bundleSpecifier, lwrVersion));
|
|
60
|
+
// Order matters:
|
|
61
|
+
// 1. "globalThis.LWR" must be defined prior to executing the shim and loader
|
|
62
|
+
// 2. the lwc module override needs to be defined before lwc is [re]defined in the custom element code (first define wins)
|
|
63
|
+
return [
|
|
64
|
+
GLOBALTHIS_LWR,
|
|
65
|
+
`Object.assign(globalThis.LWR, ${lwrConfigString});`,
|
|
66
|
+
loaderShimSource ? loaderShimSource : '',
|
|
67
|
+
lwcSpecifier ? lwcDefineOverride(lwcSpecifier) : '',
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=amd-utils.js.map
|
|
@@ -1,7 +1,25 @@
|
|
|
1
|
-
import { ProviderContext, ViewTransformPlugin } from '@lwrjs/types';
|
|
1
|
+
import type { ProviderContext, ViewTransformPlugin } from '@lwrjs/types';
|
|
2
2
|
interface SsrPluginOptions {
|
|
3
3
|
shareWorker?: boolean;
|
|
4
4
|
}
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* This is a view transformer run by the view registry during linking of a page document/route (configured in lwr.config.json[routes]).
|
|
7
|
+
* If the "experimentalSSR" bootstrap flag is on for the route, it will server-side render (SSR) each custom element found in the page HTML.
|
|
8
|
+
*
|
|
9
|
+
* SSR Flow:
|
|
10
|
+
* 1. There is a request to generate a view (ie: page document) via the UI middleware or static site generation
|
|
11
|
+
* 2. During view generation, the view registry runs all the registered view transformers (including this one)
|
|
12
|
+
* 3. This view transformer links the SSRed string for EVERY custom element (ie: root component) found in the page document:
|
|
13
|
+
* a) It requests a module which SSRs a given custom element, generated by "lwc-ssr/moduleProvider"
|
|
14
|
+
* b) A bundle is created for the generated SSR module (see "./ssr-element")
|
|
15
|
+
* c) The bundle code is run inside a worker (see "./ssr-element"), with context stored in "workerData"
|
|
16
|
+
* d) RootComponent.getProps() is run to preload data, if available
|
|
17
|
+
* e) The generated SSR module (running the worker) passes the SSRed code string back to the main thread
|
|
18
|
+
* f) The SSRed string is used to overwrite/link each custom element (eg: "<c-app></c-app>") in the document (see "stringBuilder.overwrite")
|
|
19
|
+
* g) A script containing all the serialized properties is added for hydration
|
|
20
|
+
* 4. The view/page document now contains SSRed components, which will be sent to the client
|
|
21
|
+
* 5. During bootstrap on the client, the "lwr/initSsr" module will hydrate ALL the custom elements on the page
|
|
22
|
+
*/
|
|
23
|
+
export default function lwcSsrViewTranformer(options: SsrPluginOptions, { moduleBundler }: ProviderContext): ViewTransformPlugin;
|
|
6
24
|
export {};
|
|
7
25
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,24 +1,70 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { LWC_SSR_PREFIX } from '../identity.js';
|
|
1
|
+
import { kebabCaseToModuleSpecifer } from '@lwrjs/shared-utils';
|
|
2
|
+
import { LWC_SSR_PREFIX, SSR_PROPS_ATTR, SSR_PROPS_KEY, getPropsId } from '../identity.js';
|
|
3
3
|
import { ssrElement } from './ssr-element.js';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* This is a view transformer run by the view registry during linking of a page document/route (configured in lwr.config.json[routes]).
|
|
6
|
+
* If the "experimentalSSR" bootstrap flag is on for the route, it will server-side render (SSR) each custom element found in the page HTML.
|
|
7
|
+
*
|
|
8
|
+
* SSR Flow:
|
|
9
|
+
* 1. There is a request to generate a view (ie: page document) via the UI middleware or static site generation
|
|
10
|
+
* 2. During view generation, the view registry runs all the registered view transformers (including this one)
|
|
11
|
+
* 3. This view transformer links the SSRed string for EVERY custom element (ie: root component) found in the page document:
|
|
12
|
+
* a) It requests a module which SSRs a given custom element, generated by "lwc-ssr/moduleProvider"
|
|
13
|
+
* b) A bundle is created for the generated SSR module (see "./ssr-element")
|
|
14
|
+
* c) The bundle code is run inside a worker (see "./ssr-element"), with context stored in "workerData"
|
|
15
|
+
* d) RootComponent.getProps() is run to preload data, if available
|
|
16
|
+
* e) The generated SSR module (running the worker) passes the SSRed code string back to the main thread
|
|
17
|
+
* f) The SSRed string is used to overwrite/link each custom element (eg: "<c-app></c-app>") in the document (see "stringBuilder.overwrite")
|
|
18
|
+
* g) A script containing all the serialized properties is added for hydration
|
|
19
|
+
* 4. The view/page document now contains SSRed components, which will be sent to the client
|
|
20
|
+
* 5. During bootstrap on the client, the "lwr/initSsr" module will hydrate ALL the custom elements on the page
|
|
21
|
+
*/
|
|
22
|
+
export default function lwcSsrViewTranformer(options, { moduleBundler }) {
|
|
6
23
|
return {
|
|
7
24
|
name: 'ssr-lwc-transformer',
|
|
8
|
-
async link(stringBuilder, viewContext,
|
|
25
|
+
async link(stringBuilder, viewContext, { customElements }) {
|
|
26
|
+
// SSR currently does not support locker because the module constructor returns undefined
|
|
27
|
+
// and the call to renderComponent would fail
|
|
28
|
+
if (process.env.LOCKER === 'true') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
9
31
|
if (viewContext.view.bootstrap?.experimentalSSR) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
32
|
+
// Gather all the SSRable custom elements (ie: root components) into 1 list
|
|
33
|
+
const ssrModules = [];
|
|
34
|
+
for (const { tagName, location, props } of customElements) {
|
|
35
|
+
if (location) {
|
|
36
|
+
const { startOffset, endOffset } = location;
|
|
37
|
+
const moduleSpecifier = kebabCaseToModuleSpecifer(tagName);
|
|
38
|
+
ssrModules.push({
|
|
39
|
+
startOffset,
|
|
40
|
+
endOffset,
|
|
41
|
+
props,
|
|
42
|
+
tagName,
|
|
43
|
+
specifier: `${LWC_SSR_PREFIX}${moduleSpecifier}`,
|
|
44
|
+
});
|
|
20
45
|
}
|
|
21
46
|
}
|
|
47
|
+
// SSR and gather the properties for each eligible custom element, in parallel
|
|
48
|
+
const ssrProps = {};
|
|
49
|
+
await Promise.all(ssrModules.map(({ specifier, tagName, props, startOffset, endOffset }) => {
|
|
50
|
+
return ssrElement({ specifier, props }, moduleBundler, viewContext).then(({ html, props }) => {
|
|
51
|
+
if (props) {
|
|
52
|
+
// Add the props id to the HTML for the custom element
|
|
53
|
+
// eg: <some-cmp> -> <some-cmp data-lwr-props-id="1234">
|
|
54
|
+
const propsId = getPropsId();
|
|
55
|
+
ssrProps[propsId] = props;
|
|
56
|
+
const [, remain] = html.split(`<${tagName}`);
|
|
57
|
+
html = [`<${tagName}`, ` ${SSR_PROPS_ATTR}="${propsId}"`, remain].join('');
|
|
58
|
+
}
|
|
59
|
+
// Overwrite the custom element with the SSRed component string
|
|
60
|
+
stringBuilder.overwrite(startOffset, endOffset, html);
|
|
61
|
+
});
|
|
62
|
+
}));
|
|
63
|
+
if (Object.keys(ssrProps).length) {
|
|
64
|
+
// Serialize all root component properties into a single script for the page
|
|
65
|
+
// Append the script before the custom elements; it MUST appear before the AMD shim to avoid timing issues
|
|
66
|
+
stringBuilder.prependLeft(ssrModules[0].startOffset, `<script type="application/javascript">globalThis.LWR = globalThis.LWR || {};globalThis.LWR.${SSR_PROPS_KEY} = ${JSON.stringify(ssrProps)};</script>`);
|
|
67
|
+
}
|
|
22
68
|
}
|
|
23
69
|
},
|
|
24
70
|
};
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type { Json, ModuleBundler, ViewTranformPluginContext } from '@lwrjs/types';
|
|
2
|
+
/**
|
|
3
|
+
* Create a bundle for the given SSR module and run the code in a worker.
|
|
4
|
+
* @param moduleInfo - specifier: The ID of the module, generated by "lwc-ssr/moduleProvider", which SSRs a component
|
|
5
|
+
* props: A map of the key:value property pairs parsed from the custom element attributes (ie: all string values)
|
|
6
|
+
* @param moduleBundler
|
|
7
|
+
* @param runtimeEnvironment
|
|
8
|
+
* @returns a promise to the SSRed code string
|
|
9
|
+
*/
|
|
10
|
+
export declare function ssrElement({ specifier, props: templateProps }: {
|
|
11
|
+
specifier: string;
|
|
12
|
+
props: Json;
|
|
13
|
+
}, moduleBundler: ModuleBundler, { runtimeEnvironment, runtimeParams }: ViewTranformPluginContext): Promise<{
|
|
14
|
+
html: string;
|
|
15
|
+
props: Json;
|
|
16
|
+
}>;
|
|
3
17
|
//# sourceMappingURL=ssr-element.d.ts.map
|
|
@@ -1,34 +1,61 @@
|
|
|
1
1
|
import { Worker } from 'worker_threads';
|
|
2
|
+
import getCode from './amd-utils.js';
|
|
2
3
|
const bundleConfigOverrides = {
|
|
3
4
|
exclude: [],
|
|
4
5
|
alias: {
|
|
5
|
-
lwc: '@lwc/engine-server',
|
|
6
|
+
lwc: '@lwc/engine-server', // override the default "@lwc/engine-dom" package
|
|
6
7
|
},
|
|
7
8
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Run the SSR module code in a worker, and return the results to the main thread.
|
|
11
|
+
* @param codes - Code strings which SSR a root component
|
|
12
|
+
* @returns a promise to the SSRed code string, or an error message
|
|
13
|
+
*/
|
|
14
|
+
function runCodeOnWorker(codes, workerData) {
|
|
15
|
+
const workerCode = codes.join('\n');
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
const worker = new Worker(workerCode, { eval: true, workerData });
|
|
14
18
|
worker.on('message', resolve);
|
|
15
|
-
worker.on('error',
|
|
19
|
+
worker.on('error', ({ message }) => resolve({ error: `SSR worker exited with error: ${message}` }));
|
|
16
20
|
worker.on('exit', (code) => {
|
|
17
21
|
if (code !== 0) {
|
|
18
|
-
|
|
22
|
+
resolve({ error: `SSR worker stopped with exit code: ${code}` });
|
|
19
23
|
}
|
|
20
24
|
});
|
|
21
25
|
});
|
|
22
26
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Create a bundle for the given SSR module and run the code in a worker.
|
|
29
|
+
* @param moduleInfo - specifier: The ID of the module, generated by "lwc-ssr/moduleProvider", which SSRs a component
|
|
30
|
+
* props: A map of the key:value property pairs parsed from the custom element attributes (ie: all string values)
|
|
31
|
+
* @param moduleBundler
|
|
32
|
+
* @param runtimeEnvironment
|
|
33
|
+
* @returns a promise to the SSRed code string
|
|
34
|
+
*/
|
|
35
|
+
export async function ssrElement({ specifier, props: templateProps }, moduleBundler, { runtimeEnvironment, runtimeParams }) {
|
|
36
|
+
const { bundleRecord, code, specifier: bundleSpecifier, version, } = await moduleBundler.getModuleBundle({ specifier },
|
|
37
|
+
// Ensure the bundle flag is always off,
|
|
38
|
+
// otherwise TOO much gets bundled in the module registry
|
|
39
|
+
// in ESM, resulting lwc clashes/duplication
|
|
40
|
+
{ ...runtimeEnvironment, bundle: false }, undefined, bundleConfigOverrides);
|
|
41
|
+
// Gather context to send into the SSR worker
|
|
42
|
+
const context = {
|
|
43
|
+
props: templateProps,
|
|
44
|
+
params: runtimeParams.params || {},
|
|
45
|
+
locale: runtimeParams.locale || runtimeEnvironment.defaultLocale,
|
|
46
|
+
};
|
|
47
|
+
// Get the SSR string and properties bag
|
|
48
|
+
const { error, result, props } = runtimeEnvironment.format === 'amd'
|
|
49
|
+
? await runCodeOnWorker([
|
|
50
|
+
...(await getCode(runtimeEnvironment, version.replace(/\./g, '_'), bundleRecord.includedModules.find((m) => m.startsWith('lwc/v')), bundleSpecifier)),
|
|
51
|
+
code,
|
|
52
|
+
], context)
|
|
53
|
+
: await runCodeOnWorker([code], context);
|
|
54
|
+
if (error) {
|
|
55
|
+
throw new Error(error);
|
|
29
56
|
}
|
|
30
57
|
else {
|
|
31
|
-
return result
|
|
58
|
+
return { html: result, props };
|
|
32
59
|
}
|
|
33
60
|
}
|
|
34
61
|
//# 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.7.0-alpha.
|
|
7
|
+
"version": "0.7.0-alpha.12",
|
|
8
8
|
"homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
"build/**/*.d.ts"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@lwrjs/diagnostics": "0.7.0-alpha.
|
|
37
|
-
"@lwrjs/shared-utils": "0.7.0-alpha.
|
|
36
|
+
"@lwrjs/diagnostics": "0.7.0-alpha.12",
|
|
37
|
+
"@lwrjs/shared-utils": "0.7.0-alpha.12"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@lwrjs/types": "0.7.0-alpha.
|
|
40
|
+
"@lwrjs/types": "0.7.0-alpha.12"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">=14.15.4 <17"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "785b8380a821b43b7320783dddaf20d1433216e2"
|
|
46
46
|
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
-
var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, {get: all[name], enumerable: true});
|
|
11
|
-
};
|
|
12
|
-
var __exportStar = (target, module2, desc) => {
|
|
13
|
-
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(module2))
|
|
15
|
-
if (!__hasOwnProp.call(target, key) && key !== "default")
|
|
16
|
-
__defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
|
|
17
|
-
}
|
|
18
|
-
return target;
|
|
19
|
-
};
|
|
20
|
-
var __toModule = (module2) => {
|
|
21
|
-
return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// packages/@lwrjs/lwc-ssr/src/moduleProvider/utils.ts
|
|
25
|
-
__markAsModule(exports);
|
|
26
|
-
__export(exports, {
|
|
27
|
-
createSsrBootstrapModule: () => createSsrBootstrapModule
|
|
28
|
-
});
|
|
29
|
-
var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
|
|
30
|
-
function createSsrBootstrapModule(rootComponent) {
|
|
31
|
-
return [
|
|
32
|
-
"/* This module is meant to be used in a Server context */",
|
|
33
|
-
'import { renderComponent } from "@lwc/engine-server"',
|
|
34
|
-
`import Ctor from "${rootComponent}"`,
|
|
35
|
-
"try {",
|
|
36
|
-
` const result = renderComponent("${(0, import_shared_utils.moduleSpecifierToKebabCase)(rootComponent)}", Ctor, {});`,
|
|
37
|
-
" globalThis.workerResult = { result };",
|
|
38
|
-
"} catch(err) {",
|
|
39
|
-
" globalThis.workerResult = { error: err.toString() };",
|
|
40
|
-
`}`
|
|
41
|
-
].join("\n");
|
|
42
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { moduleSpecifierToKebabCase } from '@lwrjs/shared-utils';
|
|
2
|
-
/**
|
|
3
|
-
* Create the virtual source for the application bootstrap module
|
|
4
|
-
* @returns the generated source
|
|
5
|
-
*/
|
|
6
|
-
export function createSsrBootstrapModule(rootComponent) {
|
|
7
|
-
return [
|
|
8
|
-
'/* This module is meant to be used in a Server context */',
|
|
9
|
-
'import { renderComponent } from "@lwc/engine-server"',
|
|
10
|
-
`import Ctor from "${rootComponent}"`,
|
|
11
|
-
'try {',
|
|
12
|
-
` const result = renderComponent("${moduleSpecifierToKebabCase(rootComponent)}", Ctor, {});`,
|
|
13
|
-
' globalThis.workerResult = { result };',
|
|
14
|
-
'} catch(err) {',
|
|
15
|
-
' globalThis.workerResult = { error: err.toString() };',
|
|
16
|
-
`}`,
|
|
17
|
-
].join('\n');
|
|
18
|
-
}
|
|
19
|
-
//# sourceMappingURL=utils.js.map
|