Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/keiya01/wc-ssr

SSR with web components
https://github.com/keiya01/wc-ssr

Last synced: 2 months ago
JSON representation

SSR with web components

Awesome Lists containing this project

README

        

# wc-ssr

**NOTE: This library is EXPERIMENTAL.**

`wc-ssr` is a simple Server Side Rendering Library with `Web Components`.
This lib is worked by [Declarative Shadow DOM](https://web.dev/declarative-shadow-dom/). Therefore this lib works to use Server Side Rendering on a supported browser but works to use Client Side Rendering on a not supported browser.

## SSR with Web Components

This lib work as SSR in the browser that support [Declarative Shadow DOM](https://web.dev/declarative-shadow-dom/). But this lib perform as Client Side Rendering in not supported browsers.

## Installation

```sh
npm install wc-ssr
```

**or**

```sh
yarn add wc-ssr
```

## Example

```ts
// client/AddButton/template.ts

import { html, $props, $event, $shadowroot } from "wc-ssr";

type Props = {
title: string;
onClick?: () => void;
};

export const template = (props: Props) => html`
${/* You can pass props with `$props()`. */}

${/* You must set shadowroot attribute to use ShadowDOM */}

${/* You can add event with `$event()`. */}

${props.title}



`;

```

```ts
// client/AddButton/element.ts

import { BaseElement } from "wc-ssr/client";
import { template } from "./template";

export class AddButton extends BaseElement {
constructor() {
super();
}

render() {
// `props` is injected to `this.props`.
return template({
title: this.props.title,
onClick: this.props.onClick,
});
}
}

customElements.define("add-button", AddButton);
```

**NOTE: When you use SSR feature, you can not load `BaseElement` on the server. You must avoid loading `BaseElement` like [this example](https://github.com/keiya01/wc-ssr/blob/master/example/babel.config.js#L10-L17)**. This is because, `BaseElement` inherit `HTMLElement`.

```ts
// client/AddButton/index.ts

export { template } from "./template";
if (IS_CLIENT) {
import(/* webpackMode: "eager" */ "./element");
}
```

```ts
// page.ts

import { html } from 'wc-ssr';
import { template as AddButton } from './client/AddButton';

export const renderPage = () => html`


Hello World!


${AddButton({ title: 'button', onClick: () => console.log('clicked!') })}

`;
}
```

```ts
// server.ts

import fastify, { FastifyInstance } from "fastify";
import { Server, IncomingMessage, ServerResponse } from "http";
import { htmlToString } from "wc-ssr";
import { renderPage } from './page';

type App = FastifyInstance<
Server,
IncomingMessage,
ServerResponse,
>;

const start = async () => {
const app = fastify();

app.get("/example", async (req, reply) => {
reply.header("Content-Type", "text/html; charset=utf-8");
reply.send(`



${htmlToString(renderPage())}


`);
);
});

try {
await app.listen(3000);
} catch (err) {
app.log.error(err);
process.exit(1);
}
};

start();

```

See detail in [example](https://github.com/keiya01/wc-ssr/tree/master/example).

## Usage

- [ShadowDOM](#ShadowDOM)
- [Styling](#Styling)
- [Props](#Props)
- [Event](#Event)
- [State](#State)
- [Attribute](#Attribute)
- [Lifecycle](#Lifecycle)
- [Hydration](#Hydration)
- [Server Side Rendering](#Server-Side-Rendering)

### ShadowDOM

You must use `$shadowroot` method to tell custom element use ShadowDOM.

`$shadowroot` method can take `open` or `closed`.
This is optional. Default value is `open`.

```ts
import { html, $shadowroot } from "wc-ssr";

export const template = html`


Hello World


`;
```

### Styling

You can use css with style tag.
You can set style tag inside template tag.

```ts
import { html, $shadowroot } from "wc-ssr";

const style = html`

button {
color: red;
}

`;

const CustomButton = html`


${style}
button


`;
```

You can also set multiple styles as the following.

```ts
const style = html`



div {
background-color: blue;
}

`;
```

### Props

You can pass props to component. And you can get props to be injected from BaseElement class.

```ts
import { html, $shadowroot } from "wc-ssr";
import { BaseElement } from "wc-ssr/client";

const CustomElement = html`


Hello World


${
PassProps({
text: "This is paragraph",
}) /* Pass props to PassProps component */
}


`;

class CustomElement extends BaseElement {
/* ... */
}

const PassProps = ({ text }) => html`


${text}




`;

class PassProps extends BaseElement {
constructor() {
super();
}

render() {
return PassProps({ ...this.props });
}
}
```

### Event

You can add event to element by using `$event` method.

```ts
import { html, $shadowroot, $event } from "wc-ssr";
import { BaseElement } from "wc-ssr/client";

const EventElement = ({ handleOnClick }) => html`


click me


`;

class EventElementClass extends BaseElement {
constructor() {
super();
}

handleOnClick = () => {
console.log("Clicked!!");
};

render() {
return EventElement({ handleOnClick: this.handleOnClick });
}
}
```

### State

You can define state like React. If you defined state and change it, `render()` is executed.

```ts
import { html, $shadowroot, $event } from "wc-ssr";
import { BaseElement } from "wc-ssr/client";

type Props = {
items: string[];
text: string;
handleOnChangeText: (e?: InputEvent) => void;
addItem: () => void;
};

const DefineState = ({ items, text, handleOnChangeText, addItem }) => html`



    ${items.map((item) => html`
  • ${item}
  • `)}


add item


`;

class DefineState extends BaseElement {
constructor() {
super();
this.state = {
items: [],
text: [],
};
}

addItem = () => {
this.setState({ items: [...this.state.items, this.state.text] });
};

handleOnChangeText = (e?: InputEvent) => {
const target = e?.target as HTMLInputElement;
if (target) {
this.setState({ text: target.value });
}
};

render() {
return DefineState({
items: this.state.items,
text: this.state.text,
handleOnChangeText: this.handleOnChangeText,
addItem: this.addItem,
});
}
}
```

### Attribute

_TODO_

### Lifecycle

You can use [web components lifecycle](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks) like below.

```ts
connectedCallback() {
super.connectedCallback();
console.log('connected!!');
}
```

And you can use additional lifecycle.

- `componentDidMount` ... This function is invoked when all preparation of `BaseElement` is completed.

```ts
componentDidMount() {
super.componentDidMount();
this.setState({ text: this.props.text });
}
```

### Hydration

Hydration is performed automatically by browser.

### Server Side Rendering

You can use `htmlToString` to render html on the server.

```ts
import { htmlToString, html, $shadowroot } from "wc-ssr";

type Props = {
text: string;
};

const render = ({ text }: Props) => html`


${text}




`;

htmlToString(render({ text: "Hello World" }));
```

## License

[MIT](https://github.com/keiya01/wc-ssr/blob/master/LICENSE)