Repository: denoland/fresh (TypeScript)

Issue:
[2.0] Error when using a bigint as a key
When using a bigint as a key for a component, I get this error:
```
TypeError: key.replaceAll is not a function
    at normalizeKey (https://jsr.io/@fresh/core/2.0.0-alpha.27/src/runtime/server/preact_hooks.tsx:172:14)
    at options.__b (https://jsr.io/@fresh/core/2.0.0-alpha.27/src/runtime/server/preact_hooks.tsx:207:25)
    at c.__b (file:///Users/nic/Library/Caches/deno/npm/registry.npmjs.org/preact/10.25.4/hooks/dist/hooks.mjs:1:2197)
    at e.__b (file:///Users/nic/Library/Caches/deno/npm/registry.npmjs.org/preact/10.25.4/compat/dist/compat.mjs:1:1629)
    at file:///Users/nic/Library/Caches/deno/npm/registry.npmjs.org/@preact/signals/1.3.1/dist/signals.mjs:1:1155
    at U (file:///Users/nic/Library/Caches/deno/npm/registry.npmjs.org/preact-render-to-string/6.5.12/dist/index.mjs:1:5067)
    at U (file:///Users/nic/Library/Caches/deno/npm/registry.npmjs.org/preact-render-to-string/6.5.12/dist/index.mjs:1:4876)
    at U (file:///Users/nic/Library/Caches/deno/npm/registry.npmjs.org/preact-render-to-string/6.5.12/dist/index.mjs:1:7997)
    at U (file:///Users/nic/Library/Caches/deno/npm/registry.npmjs.org/preact-render-to-string/6.5.12/dist/index.mjs:1:4876)
    at U (file:///Users/nic/Library/Caches/deno/npm/registry.npmjs.org/preact-render-to-string/6.5.12/dist/index.mjs:1:7997)
```


Discussion:

Patch:
diff --git a/src/runtime/server/preact_hooks.tsx b/src/runtime/server/preact_hooks.tsx
index 9f2c251e3cc..f47e3ab3e20 100644
--- a/src/runtime/server/preact_hooks.tsx
+++ b/src/runtime/server/preact_hooks.tsx
@@ -121,7 +121,7 @@ options[OptionsType.VNODE] = (vnode) => {
   if (typeof vnode.type === "function") {
     if (vnode.type === Partial) {
       const props = vnode.props as PartialProps;
-      const key = normalizeKey(vnode.key ?? "");
+      const key = normalizeKey(vnode.key);
       const mode = !props.mode || props.mode === "replace"
         ? PartialMode.Replace
         : props.mode === "append"
@@ -165,11 +165,10 @@ options[OptionsType.ATTR] = (name, value) => {
 
 const PATCHED = new WeakSet<VNode>();
 
-function normalizeKey(key: string | number) {
-  if (typeof key === "number") {
-    key = key.toString();
-  }
-  return key.replaceAll(":", "_");
+function normalizeKey(key: unknown): string {
+  const value = key ?? "";
+  const s = (typeof value !== "string") ? String(value) : value;
+  return s.replaceAll(":", "_");
 }
 
 const oldDiff = options[OptionsType.DIFF];
@@ -204,7 +203,7 @@ options[OptionsType.DIFF] = (vnode) => {
         if (island === undefined) {
           // Not an island, but we might need to preserve keys
           if (vnode.key !== undefined) {
-            const key = normalizeKey(vnode.key ?? "");
+            const key = normalizeKey(vnode.key);
             const originalType = vnode.type;
             vnode.type = (props) => {
               const child = h(originalType, props);
@@ -241,7 +240,7 @@ options[OptionsType.DIFF] = (vnode) => {
           const child = h(originalType, props);
           PATCHED.add(child);
 
-          const key = normalizeKey(vnode.key ?? "");
+          const key = normalizeKey(vnode.key);
           return wrapWithMarker(
             child,
             "island",


Test patch:
diff --git a/src/plugins/fs_routes/mod_test.tsx b/src/plugins/fs_routes/mod_test.tsx
index 990b42f115d..c1e957b4a4e 100644
--- a/src/plugins/fs_routes/mod_test.tsx
+++ b/src/plugins/fs_routes/mod_test.tsx
@@ -1336,3 +1336,26 @@ Deno.test("support numeric keys", async () => {
   const text = await res.text();
   expect(text).toContain("ok");
 });
+
+// Issue https://github.com/denoland/fresh/issues/2802
+Deno.test("support bigint keys", async () => {
+  const TestComponent = () => <div>foo</div>;
+
+  const server = await createServer({
+    "routes/index.tsx": {
+      default: () => {
+        return (
+          <>
+            <TestComponent key={9007199254740991n} />
+            ok
+          </>
+        );
+      },
+    },
+  });
+
+  const res = await server.get("/");
+  const text = await res.text();
+  expect(text).toContain("ok");
+  expect(text).toContain("key:9007199254740991");
+});