-
Notifications
You must be signed in to change notification settings - Fork 47k
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
[Flight] Allow custom encoding of the form action #27563
Conversation
Does this mean a form action could be implemented/called/run differently depending on the framework that implements form actions? What impact could this have on a library that uses form actions that wants to support multiple frameworks? |
The spirit of how it should be implemented is still that it behaves the same as client rendering. The nice thing about POST to same page is that it doesn't show up in URL and any user visible content so it looks more similar to the client rendering. But in general a library shouldn't need to care. There's another question of how GET forms that do update the URL like search forms should be implemented that's maybe controversial. |
c512911
to
9bab5a2
Compare
There are three parts to an RSC set up: - React - Bundler - Endpoints Most customizability is in the bundler configs. We deal with those as custom builds. To create a full set up, you need to also configure ways to expose end points for example to call a Server Action. That's typically not something the bundler is responsible for even though it's responsible for gathering the end points that needs generation. Exposing which endpoints to generate is a responsibility for the bundler. Typically a meta-framework is responsible for generating the end points. There's two ways to "call" a Server Action. Through JS and through a Form. Through JS we expose the `callServer` callback so that the framework can call the end point. Forms by default POST back to the current page with an action serialized into form data, which we have a decoder helper for. However, this is not something that React is really opinionated about just like we're not opinionated about the protocol used by callServer. This exposes an option to configure the encoding of the form props. `encodeFormAction` is to the SSR is what `callServer` is to the Browser. DiffTrain build for [8d48183](8d48183)
### React upstream changes - facebook/react#28333 - facebook/react#28334 - facebook/react#28378 - facebook/react#28377 - facebook/react#28376 - facebook/react#28338 - facebook/react#28331 - facebook/react#28336 - facebook/react#28320 - facebook/react#28317 - facebook/react#28375 - facebook/react#28367 - facebook/react#28380 - facebook/react#28368 - facebook/react#28343 - facebook/react#28355 - facebook/react#28374 - facebook/react#28362 - facebook/react#28344 - facebook/react#28339 - facebook/react#28353 - facebook/react#28346 - facebook/react#25790 - facebook/react#28352 - facebook/react#28326 - facebook/react#27688 - facebook/react#28329 - facebook/react#28332 - facebook/react#28340 - facebook/react#28327 - facebook/react#28325 - facebook/react#28324 - facebook/react#28309 - facebook/react#28310 - facebook/react#28307 - facebook/react#28306 - facebook/react#28315 - facebook/react#28318 - facebook/react#28226 - facebook/react#28308 - facebook/react#27563 - facebook/react#28297 - facebook/react#28286 - facebook/react#28284 - facebook/react#28275 - facebook/react#28145 - facebook/react#28301 - facebook/react#28224 - facebook/react#28152 - facebook/react#28296 - facebook/react#28294 - facebook/react#28279 - facebook/react#28273 - facebook/react#28269 - facebook/react#28376 - facebook/react#28338 - facebook/react#28331 - facebook/react#28336 - facebook/react#28320 - facebook/react#28317 - facebook/react#28375 - facebook/react#28367 - facebook/react#28380 - facebook/react#28368 - facebook/react#28343 - facebook/react#28355 - facebook/react#28374 - facebook/react#28362 - facebook/react#28344 - facebook/react#28339 - facebook/react#28353 - facebook/react#28346 - facebook/react#25790 - facebook/react#28352 - facebook/react#28326 - facebook/react#27688 - facebook/react#28329 - facebook/react#28332 - facebook/react#28340 - facebook/react#28327 - facebook/react#28325 - facebook/react#28324 - facebook/react#28309 - facebook/react#28310 - facebook/react#28307 - facebook/react#28306 - facebook/react#28315 - facebook/react#28318 - facebook/react#28226 - facebook/react#28308 - facebook/react#27563 - facebook/react#28297 - facebook/react#28286 - facebook/react#28284 - facebook/react#28275 - facebook/react#28145 - facebook/react#28301 - facebook/react#28224 - facebook/react#28152 - facebook/react#28296 - facebook/react#28294 - facebook/react#28279 - facebook/react#28273 - facebook/react#28269 Closes NEXT-2542 Disable ppr test for strict mode for now, @acdlite will check it and we'll sync again
There are three parts to an RSC set up: - React - Bundler - Endpoints Most customizability is in the bundler configs. We deal with those as custom builds. To create a full set up, you need to also configure ways to expose end points for example to call a Server Action. That's typically not something the bundler is responsible for even though it's responsible for gathering the end points that needs generation. Exposing which endpoints to generate is a responsibility for the bundler. Typically a meta-framework is responsible for generating the end points. There's two ways to "call" a Server Action. Through JS and through a Form. Through JS we expose the `callServer` callback so that the framework can call the end point. Forms by default POST back to the current page with an action serialized into form data, which we have a decoder helper for. However, this is not something that React is really opinionated about just like we're not opinionated about the protocol used by callServer. This exposes an option to configure the encoding of the form props. `encodeFormAction` is to the SSR is what `callServer` is to the Browser.
There are three parts to an RSC set up: - React - Bundler - Endpoints Most customizability is in the bundler configs. We deal with those as custom builds. To create a full set up, you need to also configure ways to expose end points for example to call a Server Action. That's typically not something the bundler is responsible for even though it's responsible for gathering the end points that needs generation. Exposing which endpoints to generate is a responsibility for the bundler. Typically a meta-framework is responsible for generating the end points. There's two ways to "call" a Server Action. Through JS and through a Form. Through JS we expose the `callServer` callback so that the framework can call the end point. Forms by default POST back to the current page with an action serialized into form data, which we have a decoder helper for. However, this is not something that React is really opinionated about just like we're not opinionated about the protocol used by callServer. This exposes an option to configure the encoding of the form props. `encodeFormAction` is to the SSR is what `callServer` is to the Browser. DiffTrain build for commit 8d48183.
This uses a similar technique to what we use to generate fake stack frames for server components. This generates an eval:ed wrapper function around the Server Reference proxy we create on the client. This wrapper function gets the original `name` of the action on the server and I also add a source map if `findSourceMapURL` is defined that points back to the source of the server function. For `"use server"` on the server, there's no new API. It just uses the callsite of `registerServerReference()` on the Server. We can infer the function name from the actual function on the server and we already have the `findSourceMapURL` on the client receiving it. For `"use server"` imported from the client, there's two new options added to `createServerReference()` (in addition to the optional [`encodeFormAction`](#27563)). These are only used in DEV mode. The [`findSourceMapURL`](#29708) option is the same one added in #29708. We need to pass this these references aren't created in the context of any specific request but globally. The other weird thing about this case is that this is actually a case where the compiled environment is the client so any source maps are the same as for the client layer, so the environment name here is just `"Client"`. ```diff createServerReference( id: string, callServer: CallServerCallback, encodeFormAction?: EncodeFormActionCallback, + findSourceMapURL?: FindSourceMapURLCallback, // DEV-only + functionName?: string, // DEV-only ) ``` The key is that we use the location of the `registerServerReference()`/`createServerReference()` call as the location of the function. A compiler can either emit those at the same locations as the original functions or use source maps to have those segments refer to the original location of the function (or in the case of a re-export the original location of the re-export is also a fine approximate). The compiled output must call these directly without a wrapper function because the wrapper adds a stack frame. I decided against complicated and fragile dev-only options to skip n number of frames that would just end up in prod code. The implementation just skips one frame - our own. Otherwise it'll just point all source mapping to the wrapper. We don't have a `"use server"` imported from the client implementation in the reference implementation/fixture so it's a bit tricky to test that. In the case of CJS on the server, we just use a runtime instead of compiler so it's tricky to source map those appropriately. We can implement it for ESM on the server which is the main thing we're testing in the fixture. It's easier in a real implementation where all the compilation is just one pass. It's a little tricky since we have to parse and append to other source maps but I'd like to do that as a follow up. Or maybe that's just an exercise for the reader. You can right click an action and click "Go to Definition". <img width="1323" alt="Screenshot 2024-08-17 at 6 04 27 PM" src="https://github.com/user-attachments/assets/94d379b3-8871-4671-a20d-cbf9cfbc2c6e"> For now they simply don't point to the right place but you can still jump to the right file in the fixture: <img width="1512" alt="Screenshot 2024-08-17 at 5 58 40 PM" src="https://github.com/user-attachments/assets/1ea5d665-e25a-44ca-9515-481dd3c5c2fe"> In Firefox/Safari given that the location doesn't exist in the source map yet, the browser refuses to open the file. Where as Chrome does nearest (last) line.
There are three parts to an RSC set up:
Most customizability is in the bundler configs. We deal with those as custom builds.
To create a full set up, you need to also configure ways to expose end points for example to call a Server Action. That's typically not something the bundler is responsible for even though it's responsible for gathering the end points that needs generation. Exposing which endpoints to generate is a responsibility for the bundler.
Typically a meta-framework is responsible for generating the end points.
There's two ways to "call" a Server Action. Through JS and through a Form. Through JS we expose the
callServer
callback so that the framework can call the end point.Forms by default POST back to the current page with an action serialized into form data, which we have a decoder helper for. However, this is not something that React is really opinionated about just like we're not opinionated about the protocol used by callServer.
This exposes an option to configure the encoding of the form props.
encodeFormAction
is to the SSR is whatcallServer
is to the Browser.