-
Notifications
You must be signed in to change notification settings - Fork 26
RFC: LWC server runtime proposal #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 2 commits
226e0ee
748a641
bb1efd3
b873783
ccbd9f3
e091f23
cce0698
c266a5a
7c902ce
d3231ec
d2cdaf7
3bc08a5
3f5f857
7922372
d2fddd5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,331 @@ | ||||||
| --- | ||||||
| title: LWC server runtime | ||||||
| status: DRAFTED | ||||||
| created_at: 2020-01-06 | ||||||
| updated_at: 2020-01-06 | ||||||
| pr: (leave this empty until the PR is created) | ||||||
| --- | ||||||
|
|
||||||
| # LWC server runtime | ||||||
|
|
||||||
| ## Summary | ||||||
|
|
||||||
| The LWC engine has been designed from the beginning to run in an environment with access to the DOM APIs. To accommodate server-side rendering (SSR) requirements, we need a way to decouple the engine from the DOM APIs. The existing `@lwc/engine` will be replaced by 3 new packages: | ||||||
| - `@lwc/engine-core` exposes platform-agnostic APIs and will be used by the different runtime packages to share the common logic. | ||||||
| - `@lwc/engine-dom` exposes LWC APIs available on the browser. | ||||||
| - `@lwc/engine-server` exposes LWC APIs used for server-side rendering. | ||||||
|
||||||
|
|
||||||
| ## Scope | ||||||
|
|
||||||
| LWC SSR support is a broad subject, this proposal only focuses on a subset of it. This proposal covers the following topics: | ||||||
|
|
||||||
| - How to evaluate the existing LWC engine module in a javascript context without DOM access. | ||||||
| - What LWC APIs should be exposed in both environments. | ||||||
| - How the LWC engine share the same logic when evaluated in both environments. | ||||||
pmdartus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| The following topics are considered out of the scope of this proposal: | ||||||
|
|
||||||
| - How the LWC component tree rendered on the server gets serialized to HTML and sent to the browser. | ||||||
| - How the serialized component tree gets rehydrated when processed by the browser. | ||||||
|
|
||||||
| ## Motivation | ||||||
|
|
||||||
| The first step in enabling SSR support on LWC is to be able to run the engine in a non-DOM-enabled environment. It is currently impossible to evaluate the engine in such environment due to its hard dependencies on DOM APIs. | ||||||
|
|
||||||
| While we want to share as much logic as possible between the different environments and make SSR as transparent as possible for component authors, the runtime behavior and APIs exposed by the engine greatly differs between environments. In this regard, it makes sense to break up the current `@lwc/engine` package into multiple packages specifically tailored for each environment. | ||||||
|
|
||||||
| Distributing multiple versions of the LWC engine offers the capability to expose different APIs depending on the execution context. On one hand, the current implementation of `createElement` with reaction hooks doesn't make sense in Node.js since there is no DOM tree to attach the created element. In the same way, the `renderToString` method doesn't make sense to ship in a browser. | ||||||
pmdartus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| ## Detailed design | ||||||
|
|
||||||
| The existing `@lwc/engine` will be replaced by 3 new packages: | ||||||
|
|
||||||
| - `@lwc/engine-core`: Core logic shared by the different runtimes including the rendering engine and the reactivity mechanism. This package should never be consumed directly in an application. This package is agnostic on the underlying rendering medium. It only provides APIs for building custom runtimes. | ||||||
| - `@lwc/engine-dom`: Runtime that can be used to render LWC component trees in a DOM environment. This package is built on top of `@lwc/engine-core`. | ||||||
| - `@lwc/engine-server`: Runtime used that can be used to render LWC component trees as strings. This package is built on top of `@lwc/engine-core`. | ||||||
pmdartus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| The existing `lwc` package stays untouched and will be used to distribute the different versions of the engine. From the developer perspective, the experience should be identical. | ||||||
|
|
||||||
| **`c/app/app.js`:** | ||||||
|
|
||||||
| ```js | ||||||
| import { LightningElement } from "lwc"; | ||||||
|
|
||||||
| export default class App extends LightningElement {} | ||||||
| ``` | ||||||
|
|
||||||
| **`client.js`:** | ||||||
|
|
||||||
| ```js | ||||||
| import { createElement } from "lwc"; // Aliased to @lwc/engine-dom | ||||||
| import App from "c/app"; | ||||||
|
|
||||||
| const app = createElement("c-app", { is: App }); | ||||||
| document.body.appendChild(app); | ||||||
| ``` | ||||||
|
|
||||||
| **`server.js`:** | ||||||
|
|
||||||
| ```js | ||||||
| import { createElement, renderToString } from "lwc"; // Aliased to @lwc/engine-server | ||||||
| import App from "c/app"; | ||||||
|
|
||||||
| const app = createElement("c-app", { is: App }); | ||||||
| const str = renderToString(app); | ||||||
|
|
||||||
| console.log(str); | ||||||
| ``` | ||||||
|
|
||||||
| ### `@lwc/engine-core` | ||||||
|
|
||||||
| This packages exposes the following platform-agnostic APIs: | ||||||
|
|
||||||
| - `LightningElement` | ||||||
| - `api` | ||||||
| - `track` | ||||||
| - `readonly` | ||||||
| - `wire` | ||||||
| - `setFeatureFlag` | ||||||
| - `getComponentDef` | ||||||
| - `isComponentConstructor` | ||||||
| - `getComponentConstructor` | ||||||
| - `unwrap` | ||||||
| - `registerTemplate` | ||||||
| - `registerComponent` | ||||||
| - `registerDecorators` | ||||||
| - `sanitizeAttribute` | ||||||
|
|
||||||
| The DOM APIs used by the rendered engine are injected by the runtime depending on the environment. A list of all the DOM APIs the engine depends upon can be found in the [DOM APIs usage](#dom-apis-usage) section in the Appendix. | ||||||
|
|
||||||
| ### `@lwc/engine-dom` | ||||||
|
|
||||||
| This package exposes the following APIs: | ||||||
|
|
||||||
| - `createElement` + reaction hooks | ||||||
| - `buildCustomElementConstructor` | ||||||
| - `isNodeFromTemplate` | ||||||
|
|
||||||
| This package injects the native DOM APIs into the `@lwc/engine-core` rendering engine. | ||||||
|
|
||||||
| ### `@lwc/engine-server` | ||||||
|
|
||||||
| This package exposes the following APIs: | ||||||
|
|
||||||
| - `createElement(name: string, options: { is: typeof LightningElement }): ServerHTMLElement`: This method creates a new LWC component tree. It follows the same signature as the `createElement` API from `@lwc/engine-dom`. Instead of returning a native `HTMLElement`, this method returns a `ServerHTMLElement` with the public properties, aria reflected properties and HTML global attributed. | ||||||
pmdartus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| - `renderToString(element: ServerHTMLElement): string`: This method creates an LWC component tree synchronously and serialize it to string. It accepts a single parameter, a `ServerHTMLElement` returned by `createElement` and returns the serialized string. | ||||||
pmdartus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
pmdartus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| This package injects mock DOM APIs in the `@lwc/engine-core` rendering engine. Those DOM APIs produces a lightweight DOM structure that can be serialized into a string by the `renderToString` method. As described in the Appendix, this package is also in charge of attaching on the global object a mock `CustomEvent`. | ||||||
|
||||||
| This package injects mock DOM APIs in the `@lwc/engine-core` rendering engine. Those DOM APIs produces a lightweight DOM structure that can be serialized into a string by the `renderToString` method. As described in the Appendix, this package is also in charge of attaching on the global object a mock `CustomEvent`. | |
| This package injects mock DOM APIs in the `@lwc/engine-core` rendering engine. Those DOM APIs produce a lightweight DOM structure that can be serialized into a string by the `renderToString` method. As described in the Appendix, this package is also in charge of attaching on the global object a mock `CustomEvent`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those DOM APIs produce a lightweight DOM structure
Which DOM implementation do you plan to use for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it will be our own bake implementation... We do not plan to allow user-code to talk to the dom, and if they do, we will throw or ignore. The mocks needed here (btw, I will not call them mocks, but implementation) are those APIs needed for the engine to function, so it goes hand to hand.
pmdartus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
pmdartus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also add scalability. The mocked APIs would have to be kept in sync with what's actually being used in the engine. Also the engine still wouldn't be reusable in other environments without a corresponding "mock" layer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it is the case. There is only a well-defined set of DOM APIs the engine requires to operate. Each runtime (dom and server) is in charge of implementing this contract by passing an implementation of those APIs to the engine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it has stabilized by now but when I took that approach I had to add/change multiple mocked APIs during the couple months I touched that code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that will be that challenging, we will own both packages, and we can keep them in sync.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would probably be a major version change requiring new documentation.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pmdartus "LWC Server Runtime" sounds like an application framework for running LWC, like LWR (and runtime is super overloaded). Can we rename this to "LWC Server Engine" or "LWC DOM Decoupling"?