blitz-js / next-superjson-plugin

SuperJSON Plugin for Next.js Pages and Components
192 stars 13 forks source link

Next 13 support #55

Closed Xexr closed 1 year ago

Xexr commented 1 year ago

From what I can tell, this doesn't currently work with Next 13.

I suspect this is because it is presently only active on the pages directory.

Any chance of this being updated to support the Next 13 app directory as well?

orionmiz commented 1 year ago

Since the server component doesn't use data fetching functions(getServerSideProps, ...) anymore, but started using the custom functions instead, that allows you to return data directly without any serialization/deserialization.

So once you create a server component in the app directory, this plugin gets no longer needed.

Hope it was helpful.

Xexr commented 1 year ago

Thanks for the quick response, much appreciated.

Please let me lay out my specific use case just so I can be sure I understand.

In the app folder, I have a server component that is fetching some data, however I don't want to render that data in the component directly, rather I want to pass it as a prop to a client component which will use it as it's initial data but also enable ongoing fetching itself directly.

Reason I'm doing this is because the component has to have function callbacks and Next complains when I include those in a server component and tells me they need to be in a client component.

For that initial data that I'm passing, it contains a Date object. Next still gives me a warning for it:

Warning: Only plain objects can be passed to Client Components from Server Components. Date objects are not supported.

I can get around this using superjson, so I figured there was still use for this package in next13. Is there another way I should be approaching this instead?

orionmiz commented 1 year ago

Your detailed explanation helped me a lot to understand. Thanks!

After much thought, I found a solution to pass the serialized props to the client components.

But only one big problem is still left: There's no way for the plugin to know which props of the components should be (de)serialized via SuperJSON.

Of course, It is possible to apply SuperJSON to the all components in the app directory, but I don't think it will be efficient. So those component files that want to use SuperJSON must be specified in a plugin config manually in your hands.

If you think this way is okay, I'll think about working on this feature soon.

Xexr commented 1 year ago

Thanks for getting back to me. Good to know there is still a use case and I'm not missing something obvious!

With regard to the problem you call out. Why is the app folder in next13 inherently different to the pages or components directory in earlier versions of next. How does the plugin currently know which props to (de)serialise for components in the pages/components directory?

Assuming there is some fundamental blocking reason, then a couple thoughts on your suggestion. I'm not against a plugin config somewhere that lists relevant components but thought of the following alternatives:

  1. a jsdoc style comment at the top of each component that includes an array of superjson'd prop names to (de)serialise, e.g: // superjson-props: [ currentDate ]. The plugin can ignore any components that don't include a compliant comment
  2. provide some types exportable from the plugin, e.g. sjDate that can be applied to props and which the plugin can then look for in components to know whether they should be included
orionmiz commented 1 year ago

How does the plugin currently know which props to (de)serialise for components in the pages/components directory?

Let's get this straight.

It has been clear to seperate the component which wants to get serialized props from data fetching function which wants to return serialized props by their identifier and the export statement.

So the plugin was able to wrap those function and page component into SuperJSON functions if a page module contains a data fetching function like getServerSideProps. See Example

However, introducing server components made this approach impossible.

For example:

export default function ServerComponent() {
  const date = new Date()
  return (
    <>
      {/* don't need serialization */}
      <AnotherServerComponent date={date} />

      {/* needs serialization */}
      <ClientComponent date={date} />
    </>
  )
}

As a plugin, It's impossible to know which component needs props serialization in this case.

So the plugin has no choice but to transform all the components.

Here's the expected output:

import { Wrapper } from "next-superjson-plugin/tools"
export default function ServerComponent() {
  const date = new Date()
  return (
    <>
      <Wrapper _component={AnotherServerComponent} date={date} />
      <Wrapper _component={ClientComponent}  date={date} />
    </>
  )
}

Anyway, first idea is great! but we won't have to list specific props. Also, using a statement seems more conventional, so this one on top of the codes would be enough:

"use superjson"

This statement would be used as a flag to detect whether a file is a target of (de)serialization or not with "use client" flag

Xexr commented 1 year ago

Sounds good!

So taking the "expected output" you noted, it would then look as follows assuming we have entered "use superjson" on the relevant client component?

import { Wrapper } from "next-superjson-plugin/tools"
export default function ServerComponent() {
  const date = new Date()
  return (
    <>
     {/* don't need serialization as server component */}
     <AnotherServerComponent date={date} />
     {/* needs serialization, plugin has detected this by noting the "use superjson" comment on the client component */}
     <Wrapper _component={ClientComponent}  date={date} />
    </>
  )
}
orionmiz commented 1 year ago

To be exact, Wrapper would be applied to the both AnotherServerComponent and ClientComponent

If "use superjson" is not found in a module file, plugin will skip transformation on that file.

orionmiz commented 1 year ago

Hi, I brought another idea.

export default function ServerComponent() {
  const date = new Date()
  return (
    <>
      <AnotherServerComponent date={date} />

      { /* Use "data-superjson" attribute for the component where you want to use SuperJSON */ }
      <ClientComponent date={date} data-superjson />
    </>
  )
}

The data attributes are reserved in the native DOM attributes. So the props type for ClientComponent doesn't have to be changed. (Type safe)

Finally, the codes above can be transformed by the plugin like this:

export default function ServerComponent() {
  const date = new Date()
  return (
    <>
      <AnotherServerComponent date={date} />

      { /* Use "data-superjson" attribute for the component where you want to use SuperJSON */ }
      <Wrapper _component={ClientComponent} date={date} />
    </>
  )
}

Using data-superjson attribute, "use superjson" directive won't be needed anymore.

Xexr commented 1 year ago

Sounds good to me, seems like a cleaner solution. I look forward to trying it out.

orionmiz commented 1 year ago

@Xexr Plugin v0.5.0 with this feature is released!

The basic usage("data-superjson" directive) is not changed but you can follow the updated README.md by any chance.

Please try it, and I'll waiting for your review 😄

Xexr commented 1 year ago

Great to see that its up.

Unfortunately, I'm getting an error. I've set it up as per the updated readme via experimental.swcPlugins.

I'm on next @ 13.0.5, superjson @ 1.11.0, next-superjson-plugin @ 0.5.0, and I'm using pnpm @ 7.17.1

Here's a copy of the error trace I get:

@ursa/web:dev: thread '' panicked at 'called Option::unwrap() on a None value', src/lib.rs:43:48 @ursa/web:dev: note: run with RUST_BACKTRACE=1 environment variable to display a backtrace @ursa/web:dev: thread '' panicked at 'called Option::unwrap() on a None valuethread '', thread '' panicked at 'called Option::unwrap() on a None value', src/lib.rs:43:48 @ursa/web:dev: note: run with RUST_BACKTRACE=1 environment variable to display a backtrace @ursa/web:dev: thread '' panicked at 'failed to invoke plugin: failed to invoke plugin on 'Some("/home/xexr/projects/project-ursa/t3-ursa/node_modules/next/dist/client/app-next-dev.js")' @ursa/web:dev: @ursa/web:dev: Caused by: @ursa/web:dev: 0: failed to invoke /home/xexr/projects/project-ursa/t3-ursa/node_modules/next-superjson-plugin/dist/next_superjson.wasm as js transform plugin at /home/xexr/projects/project-ursa/t3-ursa/node_modules/next-superjson-plugin/dist/next_superjson.wasm @ursa/web:dev: 1: RuntimeError: unreachable @ursa/web:dev: at ([1623]:0x13ec2d) @ursa/web:dev: at ([1617]:0x13e9d9) @ursa/web:dev: at ([1616]:0x13e956) @ursa/web:dev: at ([1602]:0x13db87) @ursa/web:dev: at ([1601]:0x13daf6) @ursa/web:dev: at ([1611]:0x13e2a6) @ursa/web:dev: at ([1705]:0x144859) @ursa/web:dev: at ([1711]:0x144da2) @ursa/web:dev: at ([935]:0xefa2c) @ursa/web:dev: at ([939]:0xf03a2) @ursa/web:dev: at ([1816]:0x1502a2) @ursa/web:dev: 2: unreachable', /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/swc-0.232.103/src/plugin.rs:228:14 @ursa/web:dev: note: run with RUST_BACKTRACE=1 environment variable to display a backtrace @ursa/web:dev: src/lib.rs:43:48 @ursa/web:dev: note: run with RUST_BACKTRACE=1 environment variable to display a backtrace @ursa/web:dev: ' panicked at 'called Option::unwrap() on a None value', src/lib.rs:43:48 @ursa/web:dev: note: run with RUST_BACKTRACE=1 environment variable to display a backtrace

orionmiz commented 1 year ago

Reference: https://github.com/blitz-js/next-superjson-plugin/issues/58

Fixed in v0.5.2. Try it!

Xexr commented 1 year ago

All working correctly now. Thanks :)

kevinsimper commented 1 year ago

I could not get it working with a basic setup, I followed the Readme and I get this error https://github.com/kevinsimper/nextjs13-superjson-poc

$ npx next dev
warn  - Port 3000 is in use, trying 3001 instead.
ready - started server on 0.0.0.0:3001, url: http://localhost:3001
warn  - You have enabled experimental feature (swcPlugins) in next.config.js.
warn  - Experimental features are not covered by semver, and may cause unexpected or broken application behavior. Use at your own risk.

event - compiled client and server successfully in 611 ms (147 modules)
wait  - compiling / (client and server)...
event - compiled client and server successfully in 167 ms (176 modules)
error - SerializableError: Error serializing `.date` returned from `getServerSideProps` in "/".
Reason: `object` ("[object Date]") cannot be serialized as JSON. Please only return JSON serializable data types.
    at isSerializable (/Users/kevinsimper/projects/greenmobility/next-superjson/node_modules/next/dist/lib/is-serializable-props.js:61:15)
    at /Users/kevinsimper/projects/greenmobility/next-superjson/node_modules/next/dist/lib/is-serializable-props.js:43:66
    at Array.every (<anonymous>)
    at isSerializable (/Users/kevinsimper/projects/greenmobility/next-superjson/node_modules/next/dist/lib/is-serializable-props.js:40:39)
    at Object.isSerializableProps (/Users/kevinsimper/projects/greenmobility/next-superjson/node_modules/next/dist/lib/is-serializable-props.js:63:12)
    at Object.renderToHTML (/Users/kevinsimper/projects/greenmobility/next-superjson/node_modules/next/dist/server/render.js:568:67)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async doRender (/Users/kevinsimper/projects/greenmobility/next-superjson/node_modules/next/dist/server/base-server.js:708:34)
    at async cacheEntry.responseCache.get.isManualRevalidate.isManualRevalidate (/Users/kevinsimper/projects/greenmobility/next-superjson/node_modules/next/dist/server/base-server.js:813:28)
    at async /Users/kevinsimper/projects/greenmobility/next-superjson/node_modules/next/dist/server/response-cache/index.js:80:36 {
  page: '/'
}
wait  - compiling /_error (client and server)...
event - compiled client and server successfully in 53 ms (177 modules)
orionmiz commented 1 year ago

@kevinsimper Have you ever cleaned your build cache? (.next)

It looks fine to me.

kevinsimper commented 1 year ago

@orionmiz Yes it was, had not crossed my mind that it was the build cache!

Thank you!