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

docs: the bridge, browser, and store that's WIP #46

Merged
merged 2 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions docs/api/bridge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
title: "@podium/bridge"
---

This package is a bridge designed to pass [JSON-RPC 2.0](https://www.jsonrpc.org/specification) messages between a web application and a native web view.

## Usage

To install:

```sh
npm install @podium/bridge
```

Import the bridge in your client-side bundle:

```js
import "@podium/bridge";
```

You should probably send messages via [@podium/browser]. That said, the bridge is available on `window['@podium'].bridge`.

```js
/** @type {import("@podium/bridge").PodiumBridge} */
const bridge = window["@podium"].bridge;

// You can listen for incoming messages, which can either be RpcRequest or RpcResponse
bridge.on("global/authentication", (message) => {
const request =
/** @type {import("@podium/bridge").RpcRequest<{ token?: string }>} */ (
message
);

if (typeof request.token === "string") {
// logged in
} else {
// logged out
}
});

// You can trigger notifications (one-way messages)
bridge.notification({
method: "global/authentication",
params: { token: null },
});

// And you can call methods and await the response
/** @type {import("@podium/bridge").RpcResponse<{ c: string }>} */
const response = await bridge.call({
method: "document/native-feature",
params: { a: "foo", b: "bar" },
});
```

## API

### `bridge.on`

Add a listener for incoming messages for a given method name.

```js
import "@podium/bridge";

/** @type {import("@podium/bridge").PodiumBridge} */
const bridge = window["@podium"].bridge;

bridge.on("global/authentication", (message) => {
const request =
/** @type {import("@podium/bridge").RpcRequest<{ token?: string }>} */ (
message
);

if (typeof request.token === "string") {
// logged in
} else {
// logged out
}
});
```

### `bridge.notification`

Send a [notification](https://www.jsonrpc.org/specification#notification) (one-way message).

```js
import "@podium/bridge";

/** @type {import("@podium/bridge").PodiumBridge} */
const bridge = window["@podium"].bridge;

bridge.notification({
method: "global/authentication",
params: { token: null },
});
```

### `bridge.call`

Send a [request](https://www.jsonrpc.org/specification#request_object) and await a [response](https://www.jsonrpc.org/specification#response_object).

```js
import "@podium/bridge";

/** @type {import("@podium/bridge").PodiumBridge} */
const bridge = window["@podium"].bridge;

/** @type {import("@podium/bridge").RpcResponse<{ c: string }>} */
const response = await bridge.call({
method: "document/native-feature",
params: { a: "foo", b: "bar" },
});
```

[@podium/browser]: /docs/api/browser
[@podium/store]: /docs/api/store
15 changes: 9 additions & 6 deletions docs/api/browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ title: "@podium/browser"
---

The `@podium/browser` module is a client-side library designed to simplify communication between a podlet and the layout, and between podlets.
The module also supports applications running in a [hybrid application](/docs/guides/hybrid#client-side-communcication) when used with [@podium/bridge](/docs/api/bridge).

For an API designed as reactive state, see [@podium/store](/docs/api/store).

## Installation

Expand Down Expand Up @@ -55,11 +58,11 @@ Publish an event for a channel and topic combination. Returns the event object p

This method takes the following arguments:

| option | default | type | required | details |
| ------- | ------- | -------- | -------- | ------------------------- |
| channel | `null` | `string` | `true` | Name of the channel |
| topic | `null` | `string` | `true` | Name of the topic |
| payload | `null` | any | `false` | The payload for the event |
| option | default | type | required | details |
| ------- | ------- | -------- | -------- | ------------------------------------------------------------------------------- |
| channel | `null` | `string` | `true` | Name of the channel. Podium reserves `system` and `view` for built-in features. |
| topic | `null` | `string` | `true` | Name of the topic. |
| payload | `null` | any | `false` | The payload for the event. |

Examples:

Expand All @@ -77,7 +80,7 @@ This method takes the following arguments:

| option | default | type | required | details |
| -------- | ------- | ---------- | -------- | --------------------------------------------------------- |
| channel | `null` | `string` | `true` | Name of the channel |
| channel | `null` | `string` | `true` | Name of the channel. |
| topic | `null` | `string` | `true` | Name of the topic |
| callback | `null` | `Function` | `true` | Callback function to be invoked. Receives an event object |

Expand Down
158 changes: 158 additions & 0 deletions docs/api/store.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
title: "@podium/store"
---

This is a client-side library that provides a reactive data store using [nanostores](https://github.com/stores/stores) on top of [@podium/browser](https://github.com/podium-lib/browser)'s `MessageBus`. It includes some ready-made stores and helpers for you to make reactive stores for your own events.

By using reactive state backed by `MessageBus` you can seamlessly share state between different parts of a Podium application. If a podlet changes the value of shared state, all other podlets using that value can update.

<!-- TODO: look into an existing nanostores plugin so we make something that fits the ecosystem -->

## Usage

To install:

```sh
npm install @podium/store
```

Use an [included store](#included-stores) in your client-side application:

```js
// store/user.js
import { $authentication } from "@podium/store";
import { computed } from "nanostores";

// You can optionally make a computed value based on the included store
export const $loggedIn = computed($authentication, (authentication) =>
Boolean(authentication.token)
);

// Colocating actions with the store makes it easier to
// see what can trigger updates.
export function logIn(token) {
$authentication.set({ token });
}

export function logOut() {
$authentication.set({ token: null });
}
```

Use the reactive store to do minimal updates of your UI when state changes. Nanostores supports multiple view frameworks:

- [React and Preact](https://github.com/stores/stores?tab=readme-ov-file#react--preact)
- [Lit](https://github.com/stores/stores?tab=readme-ov-file#lit)
- [Vue](https://github.com/stores/stores?tab=readme-ov-file#vue)
- [Vanilla JS](https://github.com/stores/stores?tab=readme-ov-file#vanilla-js)

This example shows a React component:

```js
// components/user.jsx
import { useStore } from "@nanostores/react";
import { $loggedIn } from "../stores/user.js";

export const User = () => {
const loggedIn = useStore($loggedIn);
return <p>{loggedIn ? "Welcome!" : "Please log in"}</p>;
};
```

This is the same component in Lit:

```js
// components/user.js
import { StoreController } from "@nanostores/lit";
import { $loggedIn } from "../stores/user.js";

class User extends LitElement {
loggedInController = new StoreController(this, $loggedIn);

render() {
return html`<p>
${this.loggedInController.value ? "Welcome!" : "Please log in"}
</p>`;
}
}

customElements.define("a-user", User);
```

### Create your own reactive state

By using the [included helper](#mapchannel-topic-initialvalue) you can make your reactive state sync between the different parts of a Podium application.

## API

### `$authentication`

Type: [`map`](https://github.com/stores/stores?tab=readme-ov-file#maps)

```js
import { $authentication } from "@podium/store";
```

### `atom(channel, topic, initialValue)`

Create your own [`atom`](https://github.com/nanostores/nanostores?tab=readme-ov-file#atoms) that syncs between parts of a Podium application using the [MessageBus](https://github.com/podium-lib/browser).

This method requires the following arguments:

| option | type | details |
| ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| channel | `string` | Name of the channel |
| topic | `string` | Name of the topic |
| payload | `object` | The initial value. Replaced if [`peek(channel, topic)`](https://github.com/podium-lib/browser?tab=readme-ov-file#peekchannel-topic) returns a value. |

```js
import { atom } from "@podium/store";

const $reminders = atom("reminders", "list", []);
```

### `map(channel, topic, initialValue)`

Create your own [`map`](https://github.com/nanostores/nanostores?tab=readme-ov-file#maps) that syncs between parts of a Podium application using the [MessageBus](https://github.com/podium-lib/browser).

This method requires the following arguments:

| option | type | details |
| ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| channel | `string` | Name of the channel |
| topic | `string` | Name of the topic |
| payload | `object` | The initial value. Replaced if [`peek(channel, topic)`](https://github.com/podium-lib/browser?tab=readme-ov-file#peekchannel-topic) returns a value. |

```js
import { map } from "@podium/store";

const $user = map("user", "profile", { displayName: "foobar" });
```

### `deepMap(channel, topic, initialValue)`

Create your own [`deepMap`](https://github.com/nanostores/nanostores?tab=readme-ov-file#deep-maps) that syncs between parts of a Podium application using the [MessageBus](https://github.com/podium-lib/browser).

This method requires the following arguments:

| option | type | details |
| ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| channel | `string` | Name of the channel |
| topic | `string` | Name of the topic |
| payload | `object` | The initial value. Replaced if [`peek(channel, topic)`](https://github.com/podium-lib/browser?tab=readme-ov-file#peekchannel-topic) returns a value. |

```js
import { deepMap, listenKeys } from "@podium/store";

export const $profile = deepMap({
hobbies: [
{
name: "woodworking",
friends: [{ id: 123, name: "Ron Swanson" }],
},
],
skills: [["Carpentry", "Sanding"], ["Varnishing"]],
});

listenKeys($profile, ["hobbies[0].friends[0].name", "skills[0][0]"]);
```
9 changes: 9 additions & 0 deletions docs/guides/assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,12 @@ Using the [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_comp
With `podlet.css()` the end result is a `<link />` tag in the HTML document's `<head />`. If your podlet's content renders inside a shadow DOM that CSS won't be able to reach the podlet.

With a declarative shadow DOM you have to include your own `<link />` to the CSS from inside the shadow DOM.

### Islands architecture

Podium works well with [islands architecture](https://jasonformat.com/islands-architecture/) where interactivity on the client is handled by small, isolated applications.

Especially when building your layout:

- Consider how JavaScript libraries you use handle external content (external in the sense that it is not generated by your library).
- Be mindful of how much of the document your JavaScript library hydrates.
Loading