Conversation
commit: |
|
/bigbonk review |
|
There was a problem hiding this comment.
Solid fix. The approach is sound: rewriting wrapped "use server" exports into local bindings + export { ... } before plugin-rsc's strict validation is a clean workaround that preserves semantics for both the client proxy transform and the server action transform. Tests are well-structured — they reproduce the upstream bug, verify the fix, and confirm both proxy and server transforms work on the rewritten output.
A few observations (non-blocking):
Multi-declarator export const rewriting: shouldRewriteNamedDeclaration uses .some(), so if even one declarator in a multi-declarator statement needs rewriting, the entire statement gets rewritten. For example, export const a = async () => {}, b = wrap(async () => {}) rewrites both a and b even though a doesn't need it. This is harmless — plugin-rsc accepts export { a } specifiers for direct async functions just fine — but it's worth noting. In practice, multi-declarator export statements in "use server" files are extremely rare.
containsInlineAsyncFunction is deliberately broad: It recurses into the entire initializer subtree, so something like export const config = { handler: async () => {} } would also trigger a rewrite. This is fine — in a "use server" file, non-function exports are invalid anyway, and the rewrite is harmless for plugin-rsc's downstream handling.
Test coverage gaps (consider as follow-ups):
- No test for
export default wrap(async () => {})(theExportDefaultDeclarationpath at line 67-72). The default export rewrite generates a synthetic local name__vinext_server_default__and the uniqueness logic, but neither path is exercised by the current test suite. - No test for files that contain
"use server"as a string value (not a directive) to confirm the fast-path bail-out works correctly. - No test for TypeScript-specific patterns like
export const action: ServerAction = wrap(async () => {})with type annotations (thoughparseAsthandles these fine).
The plugin ordering (wrapped exports → closure collision → plugin-rsc) is correct — the two fix plugins address orthogonal concerns (file-level directive vs inline directive) and don't interfere.
|
UnknownError: Your AI Gateway has authentication active, but you didn't provide a valid apiKey |
|
@southpolesteve Bonk workflow failed. Check the logs for details. View workflow run · To retry, trigger Bonk again. |
Summary
Fix file-level
"use server"exports that wrap an inline async action, such asnext-safe-action'sactionClient.action(async () => ...)pattern.Root Cause
@vitejs/plugin-rscvalidates file-level"use server"exports syntactically and rejects wrapped exports whose initializer is a call expression instead of a direct async function expression. That causes vinext to fail withunsupported non async functionfor valid Next.js-compatible patterns.Changes
"use server"exports into local bindings plusexport { ... }@vitejs/plugin-rscnext-safe-actionshape and the upstream strict proxy transform behaviorValidation
pnpm test tests/use-server-wrapped-exports.test.ts tests/use-server-closure-collision.test.tspnpm run checkvinext, linked it into the reporter's repro app from issue Incompatible with next-safe-action #722, and confirmedGET /changed from500 unsupported non async functionto200Closes #722.