Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: edge cases for server action #745

Closed
wants to merge 26 commits into from

Conversation

himself65
Copy link
Sponsor Contributor

I was trying to make vercel-ai + waku, but found some bugs, let me see if I can reproduce here

Copy link

vercel bot commented Jun 13, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
waku ❌ Failed (Inspect) Jul 10, 2024 4:39am

Copy link

codesandbox-ci bot commented Jun 13, 2024

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@himself65 himself65 marked this pull request as ready for review June 13, 2024 04:42
@himself65 himself65 changed the title test: cover more cases for server actions [WIP] test: cover more cases for server actions Jun 14, 2024
@himself65 himself65 changed the title [WIP] test: cover more cases for server actions fix: cover more cases for server actions Jun 18, 2024
@himself65 himself65 changed the title fix: cover more cases for server actions fix: edge cases for server action Jun 18, 2024
@@ -188,7 +198,46 @@ export async function renderRsc(
) {
// XXX This doesn't support streaming unlike busboy
const formData = parseFormData(bodyStr, contentType);
args = await decodeReply(formData);
Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are something wrong, formData could include server references which need webpack runtime @dai-shi

Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright. It's reasonable that it requires the second argument moduleMap.

const moduleMap = new Proxy({} as Record<string, ImportManifestEntry>, {
get(_target, rsfId: string): ImportManifestEntry {
const [fileId, name] = rsfId.split('#') as [string, string];
// fixme: race condition, server actions are not initialized in the first time
Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As said, I think we should initialize all server actions before running RSC otherwise it will crash for the first time

Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still need to understand the issue, but it feels like we want lazy evaluation.

.then((m: any) => {
moduleCache.set(
fileId,
Object.fromEntries(m['__waku_serverActions']),
Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks ugly

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... I hope we can improve it.

}
return {
id: fileId,
chunks: [],
Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chunks: [fileId] is not necessary after reading the source code fo next.js

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure? I mean waku's approach might be different from nextjs's one.

@@ -5,10 +5,25 @@ import { EXTENSIONS } from '../config.js';
import { extname } from '../utils/path.js';
import { parseOpts } from '../utils/swc.js';

const collectExportNames = (mod: swc.Module) => {
const collectExportNames = (
Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are other issues:

  1. non export sever action could cause runtime error
  2. nested server action could cause runtime error
const actionsMap = {
  foo: () => {
     "use server"
// ...
  } 
}

Those are exist in vercel/ai/rsc source so we need to support them

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I missed such use cases.
This pattern, inline "use server", is only allowed in the server files, right?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I made a test case #771, but it looks like it's transforming well.
Can you give me a failing case?

This is also tried:

const actionsMap = createActions({
  foo: () => {
     "use server"
     // ...
  } 
})

Copy link
Sponsor Contributor Author

@himself65 himself65 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR little mess up, but should separate into three parts:

  1. Test some RSC edge cases
  2. RSC transform plugin
  3. RSC module map resolution

Copy link
Owner

@dai-shi dai-shi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a good improvement.
If possible, we would like to split into multiple PRs and merge gradually.
(And, we definitely need unit tests for transformation. 😓 )

@@ -5,10 +5,25 @@ import { EXTENSIONS } from '../config.js';
import { extname } from '../utils/path.js';
import { parseOpts } from '../utils/swc.js';

const collectExportNames = (mod: swc.Module) => {
const collectExportNames = (
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I missed such use cases.
This pattern, inline "use server", is only allowed in the server files, right?

newCode += `
export ${name === 'default' ? name : `const ${name} =`} registerClientReference(() => { throw new Error('It is not possible to invoke a client function from the server: ${getClientId(id)}#${name}'); }, '${getClientId(id)}', '${name}');
`;
}
return newCode;
} else if (hasUseServer) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we can remove this branch. It's for another use case unless I'm mistaken.

newCode += `
if (typeof ${name} === 'function') {
registerServerReference(${name}, '${getServerId(id)}', '${name}');
}
`;
if (internal.has(name)) {
newCode += `export { ${name} };\n`;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't feel correct. Possible name collisions.

}
>
>,
{} as Record<string, Record<string, ImportManifestEntry>>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, but maybe we should have a separate PR.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -12,6 +12,16 @@ import { parseFormData } from '../utils/form.js';
import { streamToString } from '../utils/stream.js';
import { decodeActionId } from '../renderers/utils.js';

// HACK for react-server-dom-webpack without webpack
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like we should finish up #707 before this PR.

@@ -188,7 +198,46 @@ export async function renderRsc(
) {
// XXX This doesn't support streaming unlike busboy
const formData = parseFormData(bodyStr, contentType);
args = await decodeReply(formData);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright. It's reasonable that it requires the second argument moduleMap.

const moduleMap = new Proxy({} as Record<string, ImportManifestEntry>, {
get(_target, rsfId: string): ImportManifestEntry {
const [fileId, name] = rsfId.split('#') as [string, string];
// fixme: race condition, server actions are not initialized in the first time
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still need to understand the issue, but it feels like we want lazy evaluation.

.then((m: any) => {
moduleCache.set(
fileId,
Object.fromEntries(m['__waku_serverActions']),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... I hope we can improve it.

}
return {
id: fileId,
chunks: [],
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure? I mean waku's approach might be different from nextjs's one.

@himself65
Copy link
Sponsor Contributor Author

Are you sure? I mean waku's approach might be different from nextjs's one.

It depends on what's the real behavior from webpack, I need double check that

@dai-shi dai-shi mentioned this pull request Jun 28, 2024
76 tasks
@dai-shi
Copy link
Owner

dai-shi commented Jul 2, 2024

I reverted the src code, but why do all e2e tests fail... should only a new one fail??

@dai-shi dai-shi closed this in #774 Jul 4, 2024
dai-shi added a commit that referenced this pull request Jul 4, 2024
Fix some RSC case, will fix #745. In
the future, I will migrate the logic to
https://github.com/himself65/react-server and keep same output

Some issues still happening:

1. `export default async function () {"use server"}` cannot be RSCA
2. `export function a() { "use server" }` should throw error, because
it's not async

---------

Co-authored-by: daishi <daishi@axlight.com>
@dai-shi dai-shi reopened this Jul 4, 2024
himself65 and others added 17 commits July 5, 2024 00:36
…f65/20240612/actions

# Conflicts:
#	e2e/fixtures/rsc-basic/package.json
#	pnpm-lock.yaml
Fix some RSC case, will fix dai-shi#745. In
the future, I will migrate the logic to
https://github.com/himself65/react-server and keep same output

Some issues still happening:

1. `export default async function () {"use server"}` cannot be RSCA
2. `export function a() { "use server" }` should throw error, because
it's not async

---------

Co-authored-by: daishi <daishi@axlight.com>
…hi#779)

Hmm, this is going to be a big problem with ESM/CJS compat...

Maybe related: dai-shi#421, dai-shi#428
I was hoping to use `-rc.0` for a while, but it has a bug and
`-rc.<num>` has not been maintained. Let's use nightly rc version.

close dai-shi#766
@himself65
Copy link
Sponsor Contributor Author

working on decodeReply

… himself65/20240612/actions

# Conflicts:
#	e2e/fixtures/rsc-basic/package.json
#	pnpm-lock.yaml
@himself65
Copy link
Sponsor Contributor Author

PR is to complex to rebease/merge, create a new one #785

@himself65 himself65 closed this Jul 10, 2024
@himself65 himself65 deleted the himself65/20240612/actions branch July 10, 2024 07:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants