Skip to content
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

First Shadow DOMd webcomponent #6

Merged
merged 7 commits into from Feb 20, 2020
Merged

Conversation

RyanHirsch
Copy link
Collaborator

This is the first web component that does nothing but slot the server markup. It ensures we have the proper build pipeline configured. It configures both a docker build pipeline using stage builds and webpack for compiling the client side code.

It creates a new dev npm script that will start in parallel both the existing nodemon functionality and a webpack process in watch mode. It still requires the developer to manually reload to see their changes.

@RyanHirsch RyanHirsch mentioned this pull request Feb 17, 2020
5 tasks
@tbremer
Copy link
Owner

tbremer commented Feb 18, 2020

Should panel be in dependencies and not devDependencies?

Sent with GitHawk

@RyanHirsch
Copy link
Collaborator Author

I don’t think so. We are using webpack to bundle it all which is all done in a dev environment. Moving it out of dev means it would be included in the final web server docker image which is not what we intend.

@RyanHirsch
Copy link
Collaborator Author

RyanHirsch commented Feb 18, 2020

Web Components SSR

Server-side rendering of Web Components is possible, however, it is not as common or straightforward as React, Ember, Vue, or Angular. The most documented way to do this is with skatejs/ssr or run puppeteer. SSR is a bit of a challenge due to a special web component feature called Shadow DOM. Shadow DOM requires javascript execution to function, and during execution it creates a special DOM node that isn't serializable. There are a few exploratory techniques to create a <shadow-dom> component as a hack to provide serialization but it doesn't result in the same tree normal execution would and is definitely not a blessed, known strategy. Shadow DOM isn't a must use feature of Web Components it does give us access to slots (more on that later)

I think your main point though, is wanting to ensure

  1. DOM is a fully populated when view sourcing
  2. Content is available on screen as soon as possible

To that end this was my plan for the web components.

Create a candidate-card web component that would use Shadow DOM with slots (render props). This will allow the server to generate something like:

<candidate-card name="pete-buttigieg">
   <img class="candidate-image" src="/assets/images/pete-buttigieg.jpg" alt="Portrant of Pete Buttigieg.">
   <div class="candidate-meta"><h2 style="margin: 0; padding: 0;">Pete Buttigieg </h2>
   <p style="margin: .25rem 0; font-size: 1.25rem; font-weight: 500;">Delegate count:
   23</p>
</candidate-card>

The children of candidate-card in this example (img, div, p) are all available to be used in the component by leveraging a slot element.

class CandidateCard extends Component {
  ...
  template() {
    return h('slot');
  }
  ...
}

This will ultimately result in the following DOM being produced on the client

<candidate-card name="pete-buttigieg">
  #shadow-root
    <slot> pointer to the img, div, p nodes </slot>
  /shadow-root
   <img class="candidate-image" src="/assets/images/pete-buttigieg.jpg" alt="Portrant of Pete Buttigieg.">
   <div class="candidate-meta"><h2 style="margin: 0; padding: 0;">Pete Buttigieg </h2>
   <p style="margin: .25rem 0; font-size: 1.25rem; font-weight: 500;">Delegate count:
   23</p>
</candidate-card>

I've already shown today that shipping markup containing a custom element is ultimately just rendered as a "div". Which means that this slotting strategy will fulfill what I believe the two goals are for the default display of the candidate information.

What then does Web Components buy us? Well it provides for encapsulation of the hover/click behavior of #5 and a strategy to leverage browser native components for rich client-side interaction and behavior. (It is also an excuse to use them) If the additional data and interaction must be fully SSR, we may want to change this strategy. Options include, not using Shadow DOM and attempting SSR or abandoning web components.

I will also note, that JSDOM has finished support for Custom Elements after having an open issue for 4 years this past weekend. So the SSR story may be getting better.

Comment on lines +31 to +34
if (showDetails) {
return h('div', 'Details');
}
return h('slot');
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If reading through the SSR note, you ask why are we slotting at all, here is the beginning of the plan. Slot the initial/default display, but then replace that when the user interacts with the card. So basically the "details" wouldn't be SSR'd but the normal view is.

package.json Outdated
@@ -1,14 +1,32 @@
{
"name": "1990towin",
"scripts": {
"start": "NODE_ENV=dev nodemon -w ./ -e css,js,json,html ./server/index.js"
"start": "NODE_ENV=dev nodemon -w ./ -e css,js,json,html ./server/index.js",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script start is only used in dev, so it's fine to remove it if dev becomes the default script. Or move the contents of dev into start. Whatever is fine.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the dockerfile to call start and updated the start command

@@ -0,0 +1,22 @@
export function safelyDefine(
elementName: string,
customElement: CustomElementConstructor
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get an error here, maybe because CustomElementConstructor is not pulled in from anywhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an interface from the dom library specified in the tsconfig.json, it shouldn't error for you 😞

const { extname } = require('path');

const shutdown = require('./shtudown');
const shutdown = require('./shutdown');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷‍♂

webpack.config.js Show resolved Hide resolved
@tbremer tbremer merged commit ce30f27 into tbremer:master Feb 20, 2020
@RyanHirsch RyanHirsch deleted the hirsch-details branch March 3, 2020 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants