npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-distributed-components

v0.2.0

Published

Effortlessly compose client and server components.

Downloads

16

Readme

React Distributed Components

Effortlessly compose client and server components.

Introduction

The goal of this library is to enable rendering server components declaratively from client components, using familiar component composition, and in a way that is router-agnostic.

[!NOTE] This package currently depends on the react-server-dom-webpack package. You must provide a manifest that is compatible with that package. If you're using Webpack, then you can use the Webpack plugin. If you are not using Webpack, then you probably want to wait for these APIs to mature and not be dependent on a specific module bundler.

React allows composing client and server components, but only if the current component is a server component. In other words, you cannot render a server component in a client component. In addition, if you pass a server component as a prop to a client component, then React will eagerly render that component. React must render the server component in case the client component is mounted. However, there is no guarantee that the client component will be mounted.

Consider the following example:

const ServerRoutes = () => (
  <Routes>
    <Route path="/" element={<Home />} />
    <Route path="/about" element={<About />} />
  </Routes>
);

If the <Route /> component is a client component and the <Home /> and <About /> components are server components, then both the <Home /> and <About /> components will be rendered on the server. React will skip rendering the <Route /> component because it is a client component and eagerly render the <Home /> and <About /> components because they are passed as props to the client component.

Furthermore, server components are inert. They render once on the server and only on the server, and they do not react to state changes. Because of this, Meta-frameworks have decided to tightly couple server components to a router. However, it is not strictly necessary to couple server components to a router. It is possible to devise an API for rerendering server components when their props change on the client.

This library attempts to solve these issues; specifically, it allows you to:

  • Render server components declaratively in client components.
  • Render server components only when they are mounted.
  • Rerender server components when their props change on the client.
  • Cache server components on the client so that they may be unmounted and remounted without a round trip to the server.

I believe that this may provide a path for incremental adoption of server components, where it makes sense, for existing React apps.

Getting Started

To get started, install the package from the npm registry.

npm add react-distributed-components

[!WARNING] This package is experimental. I provide no warranties. Use at your own risk, and expect breaking changes.

To render a server component from a client component, use a <ServerComponent />. For example:

import { ServerComponent } from "react-distributed-components";

type HomePage = (typeof import("./HomePage.js"))["HomePage"];

const HomePage = () => (
  <ServerComponent<HomePage>
    suspense={{ fallback: "loading home page" }}
    type="HomePage"
  />
);

In this example, I have created a <HomePage /> client component that acts as a proxy to the <HomePage /> server component. When the <HomePage /> client component is mounted, it will make a request to the server to render the <HomePage /> server component.

In order to know which endpoint to call and how to render the RSC payload, some additional context is required.

import {
  ServerComponent,
  ServerComponentContext,
} from "react-distributed-components";

import ssrManifest from "./ssrManifest.json" with { type: "json" };
import { callServer } from "./callServer.js";

type HomePage = (typeof import("./HomePage.js"))["HomePage"];

const HomePage = () => (
  <ServerComponent<HomePage>
    suspense={{ fallback: "loading home page" }}
    type="HomePage"
  />
);

const App: FC<{ url: string }> = ({ url }) => {
  const { origin } = new URL(url);

  return (
    <ServerComponentContext
      value={{
        callServer,
        endpoint: `${origin}/render`,
        ssrManifest,
      }}
    >
      <HomePage />
    </ServerComponentContext>
  );
};

This package currently depends on the react-server-dom-webpack package. You must implement the CallServerCallback and provide a manifest that is compatible with that package.

Finally, the /render HTTP endpoint needs to be implemented to render the server component. Here is an example using Hono:

import { type Context, type Next, Hono } from "hono";
import { PassThrough, Readable } from "node:stream";
import {
  decodeReply,
  renderToPipeableStream,
} from "react-server-dom-webpack/server";

import clientManifest from "./clientManifest.json" with { type: "json" };
import { HomePage } from "./HomePage.js";

app.post("/render", renderServerComponent);

async function renderServerComponent(context: Context, _next: Next) {
  type Body =
    | { type: "HomePage"; props: HomePage.Props };

  const body = await decodeReply<Body>(await context.req.text());
  const { type, props } = body;

  const Component = () => {
    switch (type) {
      case "HomePage":
        return <HomePage {...props} />;
    }
  };

  const { pipe } = renderToPipeableStream(<Component />, manifest);
  const rscPayload = pipe(new PassThrough());

  return context.newResponse(Readable.toWeb(rscPayload) as ReadableStream);
}

The client component is going to make a POST request to the server. The body of the request will contain the component type and props. The endpoint is expected to respond with the RSC payload.

API

ServerComponent

Client Component

type ServerComponent = <T extends ComponentType<any>>(props: {
  /**
   * Props to be forwarded to the server component.
   */
  props?: ComponentProps<T>;
  /**
   * Optionally provide a fallback while the server component is loading.
   */
  suspense?: SuspenseProps;
  /**
   * Identifies the type of the server component. The server will use this value
   * to know which server component to render.
   */
  type: string;
}) => ReactNode;

A ServerComponent is a client component that acts as a proxy for a server component.

Example

import { ServerComponent } from "react-distributed-components";

type HomePage = (typeof import("./HomePage.js"))["HomePage"];

const HomePage = () => (
  <ServerComponent<HomePage>
    suspense={{ fallback: "loading home page" }}
    type="HomePage"
  />
);

Context

Type

type Context = {
  /**
   * An in-memory RSC payload cache keyed by component props.
   */
  cache: Map<string, Uint8Array>;
  /**
   * Forwarded to react-server-dom-webpack.
   */
  callServer?: CallServerCallback;
  /**
   * The endpoint that renders the server component. The type and props will be
   * in the POST body.
   */
  endpoint: string;
  /**
   * Forwarded to react-server-dom-webpack.
   */
  ssrManifest?: SSRManifest;
};

A ServerComponent requires context to know which endpoint to call and how to render the RSC payload.

ServerComponentContext

Client Component

type ServerComponentContext = React.Provider<Context>;

A ServerComponentContext is used to provide context to a ServerComponent.

Example

import { ServerComponentContext } from "react-distributed-components";

import ssrManifest from "./ssrManifest.json" with { type: "json" };
import { callServer } from "./callServer.js";

const App: FC<{ url: string }> = ({ url }) => {
  const { origin } = new URL(url);

  return (
    <ServerComponentContext
      value={{
        cache: new Map(),
        callServer,
        endpoint: `${origin}/render`,
        ssrManifest,
      }}
    >
      {/* children */}
    </ServerComponentContext>
  );
};