reactRuntimeSource

tcmts.cli.rsc.reactRuntimeSource · #3fodnu24 · term · runs anywhere

The browser half, for one React and its Flight (by their pinned aliases, so it shares the app's React): serverComponent() makes the stub component that renders a server component's tree, read with Flight's client. The browser components (contexts, lazy ones) a tree names are found by hash, as Parcel's runtime would load them (parcelRequire): those of the stubs rendered.

Under server rendering (the page's first paint), globalThis.__tcmSsr is set: stubs render through it instead of fetching, with one response cache per request. In the browser, the trees recorded that way are read from __TCM_TREES__ first, so hydration sees the same tree the HTML was made from.

On a static site (__TCM_STATIC__), there's no server to ask: a tree is read from the file the build kept it in (treeName), which it rendered for every page it found. The trees a page of the app shows are read before it's gone to (__tcmPrefetch(path), which useAppLinks calls when a link to it is pointed at or pressed), from the map the build keeps of them, and each is read once. A server app's are rendered by the server before a page is gone to (/__tcm/page), and kept half a minute. A server component that renders the same for the same props (one cacheableComponents finds) is asked for with GET, so a browser keeps its tree, and it's kept through server function calls.

import { treeName } from "#codebase/tcmts/cli/rsc";

export function reactRuntimeSource(
  react: string,
  flight: string,
  runtime: string,
  codec: string,
): string {
  return `import { createElement, startTransition, use, useEffect, useState } from ${JSON.stringify(react)};
import { createFromFetch, createFromReadableStream } from ${JSON.stringify(`${flight}/client`)};
import * as flightClient from ${JSON.stringify(`${flight}/client`)};
import { encode, decode } from ${JSON.stringify(codec)};
import { onServerCall, reloadOutdated, remote } from ${JSON.stringify(runtime)};

const treeName = ${treeName};

// what trees name, by hash: each as the last stub naming it rendered it (server rendering, which renders the
// pages of several builds in one process, shares one map, as it shares \`parcelRequire\`)
const known = (globalThis.__tcmKnown ??= new Map());
const LAZY = Symbol.for("react.lazy");
const CONTEXT = Symbol.for("react.context");
// Flight renders a reference as \`lazy\` does, which takes a component: a context is provided by one, and a lazy one
// rendered by one
const components = new WeakMap();
function asComponent(c) {
  if (!c || typeof c !== "object" || (c.$$typeof !== CONTEXT && c.$$typeof !== LAZY)) return c;
  let made = components.get(c);
  if (!made) {
    made = c.$$typeof === CONTEXT ? ({ value, children }) => createElement(c, { value }, children) : (props) => createElement(c, props);
    components.set(c, made);
  }
  return made;
}
globalThis.parcelRequire ??= Object.assign((id) => ({ c: asComponent(known.get(id)) }), {
  load: () => Promise.resolve(),
  extendImportMap() {},
  meta: {
    // where a lazy component's chunks are (\`<script>\`s server rendering puts in the page, so they load with it)
    get publicUrl() {
      return (globalThis.__TCM_BASE__ ?? "") + "/_app/";
    },
    // in development, where Flight asks for the code of a server stack frame (\`tcm dev\` answers)
    ...(import.meta.env?.DEV && typeof window !== "undefined" ? { devServer: globalThis.__TCM_BASE__ ?? "" } : {}),
  },
});
// a server function a server component gave a browser component (a reference to its hash): called as any server
// function is (server rendering's Flight has no calls to make)
const calls = new Map();
flightClient.setServerCallback?.((id, args) => {
  const hash = id.slice(0, id.indexOf("#"));
  let call = calls.get(hash);
  if (!call) calls.set(hash, (call = remote(hash, "a server function", true)));
  return call(...args);
});
// a lazy one a tree names starts loading as the tree arrives (its hash, in its reference's row or one of its own),
// not when React gets to it
function warm(text) {
  for (const m of text.matchAll(/"([a-z2-7]{52})"/g)) {
    const c = known.get(m[1]);
    if (c && c.$$typeof === LAZY) {
      try {
        c._init(c._payload);
      } catch {
        // (loading: React's own way of saying so)
      }
    }
  }
}
// what a response says, read as it arrives (and given to Flight as it was)
function watched(r) {
  if (!r.body) return r;
  const [mine, flights] = r.body.tee();
  void (async () => {
    const reader = mine.getReader();
    const text = new TextDecoder();
    let rest = "";
    for (;;) {
      const { value, done } = await reader.read();
      if (done) break;
      const t = rest + text.decode(value, { stream: true });
      const nl = t.lastIndexOf("\\n");
      warm(t.slice(0, nl + 1));
      rest = t.slice(nl + 1);
    }
    warm(rest);
  })().catch(() => {});
  return new Response(flights, { status: r.status, headers: r.headers });
}

function remoteError(e) {
  const err = new Error(e && e.message ? e.message : "the server failed");
  err.name = e && e.name ? e.name : "RemoteError";
  if (e && e.status) err.status = e.status;
  return err;
}

// a tree as Flight's text (one the page or a site file has)
const fromText = (text) => {
  warm(text);
  return createFromFetch(Promise.resolve(new Response(text)));
};

// server components that render the same for the same props: their trees are asked for with GET (a browser keeps
// them), and kept here through server function calls
const pure = new Set();

function fetchTree(hash, name, body) {
  if (globalThis.__TCM_STATIC__) return readTree(hash, name, body);
  const url = (globalThis.__TCM_BASE__ ?? "") + "/__tcm/rpc/" + hash;
  const sent = pure.has(hash) && body.length < 4000
    ? fetch(url + "?p=" + encodeURIComponent(body))
    : fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body });
  return createFromFetch(
    sent.then(
      async (r) => {
        if (r.status === 410) {
          reloadOutdated();
          return new Promise(() => {});
        }
        if (!r.ok) throw remoteError(decode(await r.text()));
        return watched(r);
      },
      (e) => {
        throw new Error(name + ": the server is unreachable (" + e.message + ")");
      },
    ),
  );
}

// a static site's: what the build rendered, each file read once. The page names the site's map (the file each tree
// is in, by what it was rendered from, and the trees each page shows); files are named by their content, so a
// browser keeps them
const read = new Map();
const siteFile = (rel) => {
  let t = read.get(rel);
  if (!t) {
    t = fetch((globalThis.__TCM_BASE__ ?? "") + "/_app/" + rel).then(
      (r) => (r.ok ? r.text() : Promise.reject(new Error("its file isn't there (" + r.status + ")"))),
      (e) => Promise.reject(new Error("the site is unreachable (" + e.message + ")")),
    );
    // (one that failed is read again next time)
    t.catch(() => read.delete(rel));
    read.set(rel, t);
  }
  return t;
};
const siteMap = () =>
  typeof __TCM_STATIC__ === "string" ? siteFile(__TCM_STATIC__ + ".json").then(JSON.parse) : Promise.resolve({ trees: {}, pages: {} });
const treeText = (file) => siteFile("trees/" + file + ".txt");
if (typeof __TCM_STATIC__ !== "undefined" && typeof window !== "undefined") {
  // the trees a page shows, read before it's gone to
  globalThis.__tcmPrefetch = (path) =>
    void siteMap().then((m) => { for (const k of m.pages[path] ?? []) if (m.trees[k]) treeText(m.trees[k]).catch(() => {}); }, () => {});
  // (read once the page is idle, so a link pointed at reads only its page's trees)
  (window.requestIdleCallback ?? ((f) => setTimeout(f, 200)))(() => void siteMap().catch(() => {}));
}
function readTree(hash, name, body) {
  return createFromFetch(
    (async () => {
      try {
        const file = (await siteMap()).trees[treeName(hash + body)];
        if (!file) throw new Error("not rendered");
        const text = await treeText(file);
        warm(text);
        return new Response(text);
      } catch (e) {
        throw new Error(name + (e.message === "not rendered" ? " wasn't rendered with these props when the site was built: only what its pages show was" : ": " + e.message));
      }
    })(),
  );
}

// responses by server component and props, for the current generation (each server function call starts one)
let generation = 0;
const responses = new Map();
const listeners = new Set();
onServerCall(() => {
  generation++;
  for (const [k, e] of responses) if (e.generation < generation && !e.pure) responses.delete(k);
  // a transition: what's on screen stays until the new trees are ready
  startTransition(() => {
    for (const f of listeners) f();
  });
});

// trees the server rendered into this page: the first render reads them, so hydration matches the HTML
const recorded = typeof __TCM_TREES__ === "string" ? decode(__TCM_TREES__) : {};

// a server app's: what a page of the app shows (the trees its server components render), rendered by the server
// before it's gone to, when a link to it is pointed at or pressed. Kept half a minute, or until a server function call
if (typeof __TCM_STATIC__ === "undefined" && typeof window !== "undefined" && typeof __TCM_TREES__ === "string")
  globalThis.__tcmPrefetch = (path) => {
    void fetch((globalThis.__TCM_BASE__ ?? "") + "/__tcm/page?path=" + encodeURIComponent(path))
      .then((r) => (r.ok ? r.text() : Promise.reject(r.status)))
      .then((text) => {
        const at = generation;
        for (const [key, tree] of Object.entries(decode(text))) {
          const e = responses.get(key);
          if (e && (e.generation === at || e.pure)) continue;
          // (one that renders the same for the same props is kept as any of its trees is)
          const kept = pure.has(key.slice(0, 52));
          responses.set(key, { generation: at, res: fromText(tree), pure: kept, ...(kept ? {} : { until: Date.now() + 30000 }) });
        }
      })
      .catch(() => {});
  };

function responseFor(hash, name, body) {
  const key = hash + body;
  const ssr = globalThis.__tcmSsr;
  const cache = ssr ? ssr.cache() : responses;
  const entry = cache.get(key);
  if (entry && (entry.generation === generation || entry.pure) && !(entry.until < Date.now())) return entry.res;
  const seen = !ssr && generation === 0 ? recorded[key] : undefined;
  let res;
  if (seen !== undefined) res = fromText(seen);
  else if (ssr) {
    // (rendered in this process, for this page's request)
    const bytes = new TextEncoder();
    res = createFromReadableStream(
      new ReadableStream({
        start(c) {
          ssr.render(hash, body, (text) => c.enqueue(bytes.encode(text))).then(() => c.close(), (e) => c.error(e));
        },
      }),
    );
  } else res = fetchTree(hash, name, body);
  cache.set(key, { generation, res, pure: pure.has(hash) });
  return res;
}

/**
 * A component rendered on the server: suspends until its tree arrives, and follows server function calls (but one that
 * renders the same for the same props keeps its tree).
 */
export function serverComponent(hash, name, refs, cacheable) {
  if (cacheable) pure.add(hash);
  const C = (props) => {
    for (const h in refs) known.set(h, refs[h]);
    const body = encode([props], "props of " + name);
    const [, rerender] = useState(0);
    useEffect(() => {
      const f = () => rerender((n) => n + 1);
      listeners.add(f);
      return () => listeners.delete(f);
    }, []);
    return use(responseFor(hash, name, body));
  };
  Object.defineProperty(C, "name", { value: name });
  return C;
}
`;
}

uses

used by