@mongez/react-atom
Version:
A simple state management tool for React Js.
1 lines • 5.92 kB
Source Map (JSON)
{"version":3,"file":"ssr.mjs","names":[],"sources":["../../../../@mongez/react-atom/src/ssr.tsx"],"sourcesContent":["/**\n * SSR helpers.\n *\n * These are framework-agnostic primitives for the standard SSR flow:\n *\n * server: store.snapshot() → serializeStore() → embed in HTML\n * client: readHydration() → <AtomStoreProvider initialValues={...}>\n *\n * They're intentionally small. Next.js App Router, Remix, and TanStack\n * Start each have their own preferred transport (`__NEXT_DATA__`, loader\n * payloads, streaming chunks); the helpers here only cover the \"vanilla\"\n * inline-script-tag transport. If your framework already has a typed\n * server-to-client payload (e.g. `useLoaderData()`), skip these helpers\n * and feed the snapshot straight into `<AtomStoreProvider initialValues>`.\n */\nimport type { AtomStore } from \"@mongez/atom\";\nimport React from \"react\";\n\n/**\n * The default DOM id used by {@link HydrateAtomsScript} and\n * {@link readHydration}. Override per-provider if you need to embed\n * multiple snapshots in one document.\n */\nexport const DEFAULT_HYDRATION_SCRIPT_ID = \"__mongez_atom_state\";\n\n/**\n * Build a JSON string suitable for embedding inside an HTML `<script>`\n * tag. Two safety steps beyond a plain `JSON.stringify`:\n *\n * 1. The closing tag sequence `</` is escaped to `<\\/` so an atom value\n * containing literal HTML cannot break out of the script element.\n * 2. The U+2028 / U+2029 line separators (which are valid JSON but not\n * valid JavaScript string literals) are escaped.\n */\nexport function serializeSnapshot(\n snapshot: Record<string, unknown>,\n options: {\n /**\n * Custom replacer passed through to `JSON.stringify`.\n */\n replacer?: (key: string, value: unknown) => unknown;\n /**\n * Pretty-printing indent. Defaults to 0 (compact).\n */\n space?: number;\n } = {},\n): string {\n const json = JSON.stringify(\n snapshot,\n options.replacer as any,\n options.space,\n );\n return json\n .replace(/<\\/(script)/gi, \"<\\\\/$1\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n}\n\n/**\n * Convenience that snapshots a store and serializes the result in one call.\n *\n * const payload = serializeStore(serverStore);\n * // payload is a script-safe JSON string\n */\nexport function serializeStore(\n store: AtomStore,\n options?: Parameters<typeof serializeSnapshot>[1],\n): string {\n return serializeSnapshot(store.snapshot(), options);\n}\n\nexport type HydrateAtomsScriptProps = {\n /**\n * The serialized snapshot to embed. Pass either a pre-serialized string\n * (from {@link serializeStore}) or a raw snapshot object — the component\n * will serialize it for you.\n */\n snapshot: Record<string, unknown> | string;\n /**\n * DOM id for the `<script>` element. Defaults to\n * {@link DEFAULT_HYDRATION_SCRIPT_ID}. Set this when embedding multiple\n * stores in one document (e.g. a shell + a route-level boundary).\n */\n id?: string;\n /**\n * `nonce` for CSP-protected pages.\n */\n nonce?: string;\n};\n\n/**\n * Renders an inline `<script type=\"application/json\">` carrying a store\n * snapshot for the client to pick up.\n *\n * Place this once per `<AtomStoreProvider>` you want to hydrate. The\n * matching client-side call is {@link readHydration}.\n *\n * // server\n * <AtomStoreProvider store={serverStore}>\n * <App />\n * <HydrateAtomsScript snapshot={serverStore.snapshot()} />\n * </AtomStoreProvider>\n *\n * // client root\n * <AtomStoreProvider initialValues={readHydration() ?? undefined}>\n * <App />\n * </AtomStoreProvider>\n */\nexport function HydrateAtomsScript({\n snapshot,\n id = DEFAULT_HYDRATION_SCRIPT_ID,\n nonce,\n}: HydrateAtomsScriptProps) {\n const serialized =\n typeof snapshot === \"string\" ? snapshot : serializeSnapshot(snapshot);\n\n return (\n <script\n id={id}\n type=\"application/json\"\n nonce={nonce}\n // The serializer already neutralized `</script>` and the line\n // separators, so dangerouslySetInnerHTML is safe here.\n dangerouslySetInnerHTML={{ __html: serialized }}\n />\n );\n}\n\n/**\n * Read a hydration snapshot embedded via {@link HydrateAtomsScript} from\n * the current document.\n *\n * - On the server (no `document`), returns `null`.\n * - When the script tag is missing, returns `null`.\n * - When the script body is not valid JSON, returns `null` and logs the\n * error via `console.error` (so a malformed payload is visible during\n * development but does not crash hydration).\n */\nexport function readHydration(\n id: string = DEFAULT_HYDRATION_SCRIPT_ID,\n): Record<string, unknown> | null {\n if (typeof document === \"undefined\") return null;\n const el = document.getElementById(id);\n if (!el) return null;\n try {\n return JSON.parse(el.textContent ?? \"null\") as\n | Record<string, unknown>\n | null;\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error(\n `[@mongez/react-atom] Could not parse hydration script #${id}:`,\n err,\n );\n return null;\n }\n}\n"],"mappings":";;;;;;;;;AAuBA,MAAa,8BAA8B;;;;;;;;;;AAW3C,SAAgB,kBACd,UACA,UASI,CAAC,GACG;CAMR,OALa,KAAK,UAChB,UACA,QAAQ,UACR,QAAQ,KAEA,EACP,QAAQ,iBAAiB,QAAQ,EACjC,QAAQ,WAAW,SAAS,EAC5B,QAAQ,WAAW,SAAS;AACjC;;;;;;;AAQA,SAAgB,eACd,OACA,SACQ;CACR,OAAO,kBAAkB,MAAM,SAAS,GAAG,OAAO;AACpD;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,mBAAmB,EACjC,UACA,KAAK,6BACL,SAC0B;CAI1B,OACE,oBAAC,UAAD;EACM;EACJ,MAAK;EACE;EAGP,yBAAyB,EAAE,QAT7B,OAAO,aAAa,WAAW,WAAW,kBAAkB,QAAQ,EASpB;CAC/C;AAEL;;;;;;;;;;;AAYA,SAAgB,cACd,KAAa,6BACmB;CAChC,IAAI,OAAO,aAAa,aAAa,OAAO;CAC5C,MAAM,KAAK,SAAS,eAAe,EAAE;CACrC,IAAI,CAAC,IAAI,OAAO;CAChB,IAAI;EACF,OAAO,KAAK,MAAM,GAAG,eAAe,MAAM;CAG5C,SAAS,KAAK;EAEZ,QAAQ,MACN,0DAA0D,GAAG,IAC7D,GACF;EACA,OAAO;CACT;AACF"}