Repository: dai-shi/waku
Pull request #773 (2024-07-02 10:35:47)

fix: server action transformation with `.bind`
follow up #729

vercel[bot]: [vc]: #SUdxE2ri9/iujpvQCU0GpemBvOnBIPW3F2lSklPMGsQ=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ3YWt1IiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2RhaXNoaXMtcHJvamVjdHMvd2FrdS9HRDhOVTc3ZkxDSlVycUh4dDRFRlozUUR5eTVpIiwicHJldmlld1VybCI6Indha3UtZ2l0LWZpeC1zZXJ2ZXItYWN0aW9uLXdpdGgtYmluZC1kYWlzaGlzLXByb2plY3RzLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiUEVORElORyJ9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Updated (UTC) |
| :--- | :----- | :------ | :------ |
| **waku** | 🔄 Building ([Inspect](https://vercel.com/daishis-projects/waku/GD8NU77fLCJUrqHxt4EFZ3QDyy5i)) | [Visit Preview](https://waku-git-fix-server-action-with-bind-daishis-projects.vercel.app) | Jul 2, 2024 10:35am |


codesandbox-ci[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/dai-shi/waku/pr/773/builds/519209) or the icon next to each commit SHA.

Patch:
diff --git a/packages/waku/src/lib/plugins/vite-plugin-rsc-transform.ts b/packages/waku/src/lib/plugins/vite-plugin-rsc-transform.ts
index cbf0ba14..79969145 100644
--- a/packages/waku/src/lib/plugins/vite-plugin-rsc-transform.ts
+++ b/packages/waku/src/lib/plugins/vite-plugin-rsc-transform.ts
@@ -81,17 +81,18 @@ const createStringLiteral = (value: string): swc.StringLiteral => ({
   span: { start: 0, end: 0, ctxt: 0 },
 });
 
+const createCallExpression = (
+  callee: swc.Expression,
+  args: swc.Expression[],
+): swc.CallExpression => ({
+  type: 'CallExpression',
+  callee,
+  arguments: args.map((expression) => ({ expression })),
+  span: { start: 0, end: 0, ctxt: 0 },
+});
+
 const serverActionsInitCode = swc.parseSync(`
 import { registerServerReference as __waku_registerServerReference } from 'react-server-dom-webpack/server.edge';
-export const __waku_serverActions = new Map();
-let __waku_actionIndex = 0;
-function __waku_registerServerAction(fn, actionId) {
-  const actionName = 'action' + __waku_actionIndex++;
-  __waku_registerServerReference(fn, actionId, actionName);
-  // FIXME this can cause memory leaks
-  __waku_serverActions.set(actionName, fn);
-  return fn;
-}
 `).body;
 
 type FunctionWithBlockBody = (
@@ -100,101 +101,284 @@ type FunctionWithBlockBody = (
   | swc.ArrowFunctionExpression
 ) & { body: swc.BlockStatement };
 
+const isUseServerDirective = (node: swc.Node) =>
+  node.type === 'ExpressionStatement' &&
+  (node as swc.ExpressionStatement).expression.type === 'StringLiteral' &&
+  ((node as swc.ExpressionStatement).expression as swc.StringLiteral).value ===
+    'use server';
+
 const isServerAction = (node: swc.Node): node is FunctionWithBlockBody =>
   (node.type === 'FunctionDeclaration' ||
     node.type === 'FunctionExpression' ||
     node.type === 'ArrowFunctionExpression') &&
   (node as { body?: { type: string } }).body?.type === 'BlockStatement' &&
-  (node as FunctionWithBlockBody).body.stmts.some(
-    (s) =>
-      s.type === 'ExpressionStatement' &&
-      s.expression.type === 'StringLiteral' &&
-      s.expression.value === 'use server',
+  (node as FunctionWithBlockBody).body.stmts.some(isUseServerDirective);
+
+const prependArgsToFn = <Fn extends FunctionWithBlockBody>(
+  fn: Fn,
+  args: string[],
+): Fn => {
+  if (fn.type === 'ArrowFunctionExpression') {
+    return {
+      ...fn,
+      params: [...args.map(createIdentifier), ...fn.params],
+      body: {
+        type: 'BlockStatement',
+        stmts: fn.body.stmts.filter((stmt) => !isUseServerDirective(stmt)),
+        span: { start: 0, end: 0, ctxt: 0 },
+      },
+    };
+  }
+  return {
+    ...fn,
+    params: [
+      ...args.map((arg) => ({
+        type: 'Parameter',
+        pat: createIdentifier(arg),
+        span: { start: 0, end: 0, ctxt: 0 },
+      })),
+      ...fn.params,
+    ],
+    body: {
+      type: 'BlockStatement',
+      stmts: fn.body.stmts.filter((stmt) => !isUseServerDirective(stmt)),
+      span: { start: 0, end: 0, ctxt: 0 },
+    },
+  };
+};
+
+// HACK this doesn't work for 100% of cases
+const collectIndentifiers = (node: swc.Node, ids: Set<string>) => {
+  if (node.type === 'Identifier') {
+    ids.add((node as swc.Identifier).value);
+  } else if (node.type === 'MemberExpression') {
+    collectIndentifiers((node as swc.MemberExpression).object, ids);
+  } else if (node.type === 'KeyValuePatternProperty') {
+    collectIndentifiers((node as swc.KeyValuePatternProperty).key, ids);
+  } else if (node.type === 'AssignmentPatternProperty') {
+    collectIndentifiers((node as swc.AssignmentPatternProperty).key, ids);
+  } else {
+    Object.values(node).forEach((value) => {
+      if (Array.isArray(value)) {
+        value.forEach((v) => collectIndentifiers(v, ids));
+      } else if (typeof value === 'object' && value !== null) {
+        collectIndentifiers(value, ids);
+      }
+    });
+  }
+};
+
+// HACK this doesn't work for 100% of cases
+const collectLocalNames = (
+  fn: swc.Fn | swc.ArrowFunctionExpression,
+  ids: Set<string>,
+) => {
+  fn.params.forEach((param) => {
+    collectIndentifiers(param, ids);
+  });
+  let stmts: swc.Statement[];
+  if (!fn.body) {
+    stmts = [];
+  } else if (fn.body?.type === 'BlockStatement') {
+    stmts = fn.body.stmts;
+  } else {
+    // body is Expression
+    stmts = [
+      {
+        type: 'ReturnStatement',
+        argument: fn.body,
+        span: { start: 0, end: 0, ctxt: 0 },
+      },
+    ];
+  }
+  for (const stmt of stmts) {
+    if (stmt.type === 'VariableDeclaration') {
+      for (const decl of stmt.declarations) {
+        collectIndentifiers(decl.id, ids);
+      }
+    }
+  }
+};
+
+const collectClosureVars = (
+  parentFn: swc.Fn | swc.ArrowFunctionExpression | undefined,
+  fn: FunctionWithBlockBody,
+): string[] => {
+  const parentFnVarNames = new Set<string>();
+  if (parentFn) {
+    collectLocalNames(parentFn, parentFnVarNames);
+  }
+  const fnVarNames = new Set<string>();
+  collectIndentifiers(fn, fnVarNames);
+  const varNames = Array.from(parentFnVarNames).filter((n) =>
+    fnVarNames.has(n),
   );
+  return varNames;
+};
 
 const transformServerActions = (
   mod: swc.Module,
   getActionId: () => string,
 ): swc.Module | void => {
-  let hasServerActions = false;
-  const registerServerAction: {
-    (fn: swc.FunctionDeclaration): swc.ExpressionStatement;
-    (
-      fn: swc.FunctionExpression | swc.ArrowFunctionExpression,
-    ): swc.CallExpression;
-  } = (fn): any => {
-    hasServerActions = true;
-    const exp: swc.CallExpression = {
-      type: 'CallExpression',
-      callee: createIdentifier('__waku_registerServerAction'),
-      arguments: [
-        { expression: fn.type === 'FunctionDeclaration' ? fn.identifier : fn },
-        { expression: createStringLiteral(getActionId()) },
-      ],
-      span: { start: 0, end: 0, ctxt: 0 },
-    };
-    if (fn.type !== 'FunctionDeclaration') {
-      return exp;
+  let serverActionIndex = 0;
+  const serverActions = new Map<
+    number,
+    readonly [FunctionWithBlockBody, string[]]
+  >();
+  const registerServerAction = (
+    parentFn: swc.Fn | swc.ArrowFunctionExpression | undefined,
+    fn: FunctionWithBlockBody,
+  ): swc.CallExpression => {
+    const closureVars = collectClosureVars(parentFn, fn);
+    serverActions.set(++serverActionIndex, [fn, closureVars]);
+    const name = '__waku_action' + serverActionIndex;
+    if (fn.type === 'FunctionDeclaration') {
+      fn.identifier = createIdentifier(name);
     }
-    return {
-      type: 'ExpressionStatement',
-      expression: exp,
-      span: { start: 0, end: 0, ctxt: 0 },
-    };
+    return createCallExpression(
+      {
+        type: 'MemberExpression',
+        object: createIdentifier(name),
+        property: createIdentifier('bind'),
+        span: { start: 0, end: 0, ctxt: 0 },
+      },
+      [
+        createIdentifier('null'),
+        ...closureVars.map((v) => createIdentifier(v)),
+      ],
+    );
   };
-  const handleStatements = (stmts: swc.Statement[] | swc.ModuleItem[]) => {
-    for (let i = 0; i < stmts.length; ++i) {
-      const stmt = stmts[i]!;
-      if (isServerAction(stmt)) {
-        const registerStmt = registerServerAction(stmt);
-        stmts.splice(++i, 0, registerStmt);
-      }
+  const handleDeclaration = (
+    parentFn: swc.Fn | swc.ArrowFunctionExpression | undefined,
+    decl: swc.Declaration,
+  ) => {
+    if (isServerAction(decl)) {
+      const callExp = registerServerAction(parentFn, Object.assign({}, decl));
+      const newDecl: swc.VariableDeclaration = {
+        type: 'VariableDeclaration',
+        kind: 'const',
+        declare: false,
+        declarations: [
+          {
+            type: 'VariableDeclarator',
+            id: createIdentifier(decl.identifier.value),
+            init: callExp,
+            definite: false,
+            span: { start: 0, end: 0, ctxt: 0 },
+          },
+        ],
+        span: { start: 0, end: 0, ctxt: 0 },
+      };
+      Object.keys(decl).forEach((key) => {
+        delete decl[key as keyof typeof decl];
+      });
+      Object.assign(decl, newDecl);
     }
   };
-  const handleExpression = (exp: swc.Expression) => {
+  const handleExpression = (
+    parentFn: swc.Fn | swc.ArrowFunctionExpression | undefined,
+    exp: swc.Expression,
+  ) => {
     if (isServerAction(exp)) {
-      const callExp = registerServerAction(Object.assign({}, exp));
+      const callExp = registerServerAction(parentFn, Object.assign({}, exp));
       Object.keys(exp).forEach((key) => {
         delete exp[key as keyof typeof exp];
       });
       Object.assign(exp, callExp);
     }
   };
-  const walk = (node: swc.Node) => {
+  const walk = (
+    parentFn: swc.Fn | swc.ArrowFunctionExpression | undefined,
+    node: swc.Node,
+  ) => {
     // FIXME do we need to walk the entire tree? feels inefficient
     Object.values(node).forEach((value) => {
+      const fn =
+        node.type === 'FunctionDeclaration' ||
+        node.type === 'FunctionExpression' ||
+        node.type === 'ArrowFunctionExpression'
+          ? (node as swc.Fn | swc.ArrowFunctionExpression)
+          : parentFn;
       (Array.isArray(value) ? value : [value]).forEach((v) => {
         if (typeof v?.type === 'string') {
-          walk(v);
+          walk(fn, v);
         } else if (typeof v?.expression?.type === 'string') {
-          walk(v.expression);
+          walk(fn, v.expression);
         }
       });
     });
-    if (node.type === 'Module') {
-      const { body } = node as swc.Module;
-      handleStatements(body);
-    } else if (node.type === 'BlockStatement') {
-      const { stmts } = node as swc.BlockStatement;
-      handleStatements(stmts);
+    if (node.type === 'FunctionDeclaration') {
+      handleDeclaration(parentFn, node as swc.FunctionDeclaration);
     } else if (
       node.type === 'FunctionExpression' ||
       node.type === 'ArrowFunctionExpression'
     ) {
       handleExpression(
+        parentFn,
         node as swc.FunctionExpression | swc.ArrowFunctionExpression,
       );
     }
   };
-  walk(mod);
-  if (!hasServerActions) {
+  walk(undefined, mod);
+  if (!serverActionIndex) {
     return;
   }
+  const serverActionsCode = [...serverActionsInitCode];
+  for (const [actionIndex, [actionFn, closureVars]] of serverActions) {
+    if (actionFn.type === 'FunctionDeclaration') {
+      const stmt1: swc.ExportDeclaration = {
+        type: 'ExportDeclaration',
+        declaration: prependArgsToFn(actionFn, closureVars),
+        span: { start: 0, end: 0, ctxt: 0 },
+      };
+      const stmt2: swc.ExpressionStatement = {
+        type: 'ExpressionStatement',
+        expression: createCallExpression(
+          createIdentifier('__waku_registerServerReference'),
+          [
+            createIdentifier(actionFn.identifier.value),
+            createStringLiteral(getActionId()),
+            createStringLiteral('__waku_action' + actionIndex),
+          ],
+        ),
+        span: { start: 0, end: 0, ctxt: 0 },
+      };
+      serverActionsCode.push(stmt1, stmt2);
+    } else {
+      const stmt: swc.ExportDeclaration = {
+        type: 'ExportDeclaration',
+        declaration: {
+          type: 'VariableDeclaration',
+          kind: 'const',
+          declare: false,
+          declarations: [
+            {
+              type: 'VariableDeclarator',
+              id: createIdentifier('__waku_action' + actionIndex),
+              init: createCallExpression(
+                createIdentifier('__waku_registerServerReference'),
+                [
+                  prependArgsToFn(actionFn, closureVars),
+                  createStringLiteral(getActionId()),
+                  createStringLiteral('__waku_action' + actionIndex),
+                ],
+              ),
+              definite: false,
+              span: { start: 0, end: 0, ctxt: 0 },
+            },
+          ],
+          span: { start: 0, end: 0, ctxt: 0 },
+        },
+        span: { start: 0, end: 0, ctxt: 0 },
+      };
+      serverActionsCode.push(stmt);
+    }
+  }
   const lastImportIndex = mod.body.findIndex(
     (node) =>
       node.type !== 'ExpressionStatement' && node.type !== 'ImportDeclaration',
   );
-  mod.body.splice(lastImportIndex, 0, ...serverActionsInitCode);
+  mod.body.splice(lastImportIndex, 0, ...serverActionsCode);
   return mod;
 };
 
@@ -207,14 +391,14 @@ const transformServer = (
   const ext = extname(id);
   const mod = swc.parseSync(code, parseOpts(ext));
   let hasUseClient = false;
-  let hasUseServer = false;
+  let hasUseServer: swc.ExpressionStatement | undefined;
   for (const item of mod.body) {
     if (item.type === 'ExpressionStatement') {
       if (item.expression.type === 'StringLiteral') {
         if (item.expression.value === 'use client') {
           hasUseClient = true;
         } else if (item.expression.value === 'use server') {
-          hasUseServer = true;
+          hasUseServer = item;
         }
       }
     } else {
@@ -234,19 +418,34 @@ export ${name === 'default' ? name : `const ${name} =`} registerClientReference(
     }
     return newCode;
   } else if (hasUseServer) {
+    const useServerPosStart = hasUseServer.span.start - mod.span.start;
+    const useServerPosEnd = hasUseServer.span.end - mod.span.start;
+    const lastImportIndex = mod.body.findIndex(
+      (node) =>
+        node.type !== 'ExpressionStatement' &&
+        node.type !== 'ImportDeclaration',
+    );
+    let lastImportPos =
+      lastImportIndex === -1 ? 0 : mod.body[lastImportIndex]!.span.end;
+    if (lastImportIndex < useServerPosEnd) {
+      lastImportPos = useServerPosEnd;
+    }
     const exportNames = collectExportNames(mod);
-    let newCode =
-      code +
+    const newCode = [
+      code.slice(0, useServerPosStart),
+      code.slice(useServerPosEnd, lastImportPos),
       `
-import { registerServerReference } from 'react-server-dom-webpack/server.edge';
-`;
-    for (const name of exportNames) {
-      newCode += `
+import { registerServerReference as __waku_registerServerReference } from 'react-server-dom-webpack/server.edge';
+`,
+      code.slice(lastImportPos),
+      ...[...exportNames].map(
+        (name) => `
 if (typeof ${name} === 'function') {
-  registerServerReference(${name}, '${getServerId(id)}', '${name}');
+  __waku_registerServerReference(${name}, '${getServerId(id)}', '${name}');
 }
-`;
-    }
+`,
+      ),
+    ].join('');
     return newCode;
   }
   // transform server actions in server components
diff --git a/packages/waku/src/lib/renderers/rsc-renderer.ts b/packages/waku/src/lib/renderers/rsc-renderer.ts
index f0a994e1..fd96cb0b 100644
--- a/packages/waku/src/lib/renderers/rsc-renderer.ts
+++ b/packages/waku/src/lib/renderers/rsc-renderer.ts
@@ -203,7 +203,7 @@ export async function renderRsc(
       }
       mod = await loadModule(fileId.slice('@id/'.length));
     }
-    const fn = mod.__waku_serverActions?.get(name) || mod[name] || mod;
+    const fn = mod[name] || mod;
     return renderWithContextWithAction(context, fn, args);
   }
 


Test patch:
diff --git a/packages/waku/tests/vite-plugin-rsc-transform-internals.test.ts b/packages/waku/tests/vite-plugin-rsc-transform-internals.test.ts
index 31839175..a480f487 100644
--- a/packages/waku/tests/vite-plugin-rsc-transform-internals.test.ts
+++ b/packages/waku/tests/vite-plugin-rsc-transform-internals.test.ts
@@ -53,28 +53,27 @@ export const log = (mesg) => {
 `;
     expect(await transform(code, '/src/App.tsx', { ssr: true }))
       .toMatchInlineSnapshot(`
-      "
-      'use server';
-
-      export const log = (mesg) => {
-        console.log(mesg);
-      };
+        "
+        import { registerServerReference as __waku_registerServerReference } from 'react-server-dom-webpack/server.edge';
+        ;
 
-      import { registerServerReference } from 'react-server-dom-webpack/server.edge';
+        export const log = (mesg) => {
+          console.log(mesg);
+        };
 
-      if (typeof log === 'function') {
-        registerServerReference(log, '/src/App.tsx', 'log');
-      }
-      "
-    `);
+        if (typeof log === 'function') {
+          __waku_registerServerReference(log, '/src/App.tsx', 'log');
+        }
+        "
+      `);
   });
 
   test('inline use server (function declaration)', async () => {
     const code = `
-export default function App() {
+export default function App({ a }) {
   function log(mesg) {
     'use server';
-    console.log(mesg);
+    console.log(mesg, a);
   }
   return <Hello log={log} />;
 }
@@ -82,20 +81,12 @@ export default function App() {
     expect(await transform(code, '/src/App.tsx', { ssr: true }))
       .toMatchInlineSnapshot(`
         "import { registerServerReference as __waku_registerServerReference } from 'react-server-dom-webpack/server.edge';
-        export const __waku_serverActions = new Map();
-        let __waku_actionIndex = 0;
-        function __waku_registerServerAction(fn, actionId) {
-            const actionName = 'action' + __waku_actionIndex++;
-            __waku_registerServerReference(fn, actionId, actionName);
-            __waku_serverActions.set(actionName, fn);
-            return fn;
+        export function __waku_action1(a, mesg) {
+            console.log(mesg, a);
         }
-        export default function App() {
-            function log(mesg) {
-                'use server';
-                console.log(mesg);
-            }
-            __waku_registerServerAction(log, "/src/App.tsx");
+        __waku_registerServerReference(__waku_action1, "/src/App.tsx", "__waku_action1");
+        export default function App({ a }) {
+            const log = __waku_action1.bind(null, a);
             return <Hello log={log}/>;
         }
         "
@@ -105,65 +96,53 @@ export default function App() {
   test('inline use server (const function expression)', async () => {
     const code = `
 export default function App() {
-  const log = function (mesg) {
+  const rand = Math.random();
+  const log = function (mesg, rand) {
     'use server';
-    console.log(mesg);
+    console.log(mesg, rand);
   };
   return <Hello log={log} />;
 }
 `;
     expect(await transform(code, '/src/App.tsx', { ssr: true }))
       .toMatchInlineSnapshot(`
-      "import { registerServerReference as __waku_registerServerReference } from 'react-server-dom-webpack/server.edge';
-      export const __waku_serverActions = new Map();
-      let __waku_actionIndex = 0;
-      function __waku_registerServerAction(fn, actionId) {
-          const actionName = 'action' + __waku_actionIndex++;
-          __waku_registerServerReference(fn, actionId, actionName);
-          __waku_serverActions.set(actionName, fn);
-          return fn;
-      }
-      export default function App() {
-          const log = __waku_registerServerAction(function(mesg) {
-              'use server';
-              console.log(mesg);
-          }, "/src/App.tsx");
-          return <Hello log={log}/>;
-      }
-      "
-    `);
+        "import { registerServerReference as __waku_registerServerReference } from 'react-server-dom-webpack/server.edge';
+        export const __waku_action1 = __waku_registerServerReference(function(rand, mesg, rand) {
+            console.log(mesg, rand);
+        }, "/src/App.tsx", "__waku_action1");
+        export default function App() {
+            const rand = Math.random();
+            const log = __waku_action1.bind(null, rand);
+            return <Hello log={log}/>;
+        }
+        "
+      `);
   });
 
   test('inline use server (const arrow function)', async () => {
     const code = `
+const now = Date.now();
 export default function App() {
   const log = (mesg) => {
     'use server';
-    console.log(mesg);
+    console.log(mesg, now);
   };
   return <Hello log={log} />;
 }
 `;
     expect(await transform(code, '/src/App.tsx', { ssr: true }))
       .toMatchInlineSnapshot(`
-      "import { registerServerReference as __waku_registerServerReference } from 'react-server-dom-webpack/server.edge';
-      export const __waku_serverActions = new Map();
-      let __waku_actionIndex = 0;
-      function __waku_registerServerAction(fn, actionId) {
-          const actionName = 'action' + __waku_actionIndex++;
-          __waku_registerServerReference(fn, actionId, actionName);
-          __waku_serverActions.set(actionName, fn);
-          return fn;
-      }
-      export default function App() {
-          const log = __waku_registerServerAction((mesg)=>{
-              'use server';
-              console.log(mesg);
-          }, "/src/App.tsx");
-          return <Hello log={log}/>;
-      }
-      "
-    `);
+        "import { registerServerReference as __waku_registerServerReference } from 'react-server-dom-webpack/server.edge';
+        export const __waku_action1 = __waku_registerServerReference((mesg)=>{
+            console.log(mesg, now);
+        }, "/src/App.tsx", "__waku_action1");
+        const now = Date.now();
+        export default function App() {
+            const log = __waku_action1.bind(null);
+            return <Hello log={log}/>;
+        }
+        "
+      `);
   });
 
   test('inline use server (in an object)', async () => {
@@ -180,26 +159,18 @@ export default function App() {
 `;
     expect(await transform(code, '/src/App.tsx', { ssr: true }))
       .toMatchInlineSnapshot(`
-      "import { registerServerReference as __waku_registerServerReference } from 'react-server-dom-webpack/server.edge';
-      export const __waku_serverActions = new Map();
-      let __waku_actionIndex = 0;
-      function __waku_registerServerAction(fn, actionId) {
-          const actionName = 'action' + __waku_actionIndex++;
-          __waku_registerServerReference(fn, actionId, actionName);
-          __waku_serverActions.set(actionName, fn);
-          return fn;
-      }
-      const actions = {
-          log: __waku_registerServerAction((mesg)=>{
-              'use server';
-              console.log(mesg);
-          }, "/src/App.tsx")
-      };
-      export default function App() {
-          return <Hello log={actions.log}/>;
-      }
-      "
-    `);
+        "import { registerServerReference as __waku_registerServerReference } from 'react-server-dom-webpack/server.edge';
+        export const __waku_action1 = __waku_registerServerReference((mesg)=>{
+            console.log(mesg);
+        }, "/src/App.tsx", "__waku_action1");
+        const actions = {
+            log: __waku_action1.bind(null)
+        };
+        export default function App() {
+            return <Hello log={actions.log}/>;
+        }
+        "
+      `);
   });
 });