inertiajs / inertia

Inertia.js lets you quickly build modern single-page React, Vue and Svelte apps using classic server-side routing and controllers.
https://inertiajs.com
MIT License
6.02k stars 405 forks source link

Svelte 5: Minimalest upgrade #1872

Open jamesst20 opened 1 month ago

jamesst20 commented 1 month ago

Half-Breaking change

Svelte 5 changed how SSR rendering works. Under Svelte 3&4, you can call MyComponent.render. Under Svelte 5 you must import the render method from a package that doesn't exist under Svelte 3 & 4 making impossible to have compatibility for both unless we extract the component rendering outside.

I have updated the setup function to return the rendered result when in SSR mode.

// ssr.js
createServer((page) =>
  createInertiaApp({
    page,
    resolve: (name) => { ... },
    setup({ App, props }) {
      // Svelte 4: return App.render(props)
      // Svelte 5:
      return render(App, { props })
    },
  }),
)

I have added a warning when the implementation is not ok.

throw new Error(`setup(...) must return rendered result when in SSR mode.`)

I call this an half-breaking change because Svelte 4 apps will not break. I added a fallback with warning:

    if (!result && typeof (SSR as SSRComponentType).render !== 'function') {
      throw new Error(`setup(...) must return rendered result when in SSR mode.`)
    } else if (!result) {
      console.warn('Deprecated: in SSR mode setup(...) must be defined and must return rendered result in SSR mode.')
      console.warn('For Svelte 5: `return render(App, { props })` or for Svelte 4: `return App.render(props)`')
      result ||= (SSR as SSRComponentType).render!({ id, initialPage })
    }

Warning to Svelte 5 users

punyflash commented 3 weeks ago

I think it would be more reasonable to make it in a same way as Vue and React adapters works: just expose SSR component instead of App in setup function in case app is server-rendered and let user return rendered content:

createServer(page => createInertiaApp({
    page,
    resolve: name => resolvePageComponent(`./pages/${name}.svelte`, import.meta.glob('./pages/**/*.svelte')),
    setup: ({App, props}) => render(App, { props })
    // // Svelte 4
    // setup: ({App, props}) => App.render(props)
}))
jamesst20 commented 2 weeks ago

I think it would be more reasonable to make it in a same way as Vue and React adapters works: just expose SSR component instead of App in setup function in case app is server-rendered and let user return rendered content:

createServer(page => createInertiaApp({
    page,
    resolve: name => resolvePageComponent(`./pages/${name}.svelte`, import.meta.glob('./pages/**/*.svelte')),
    setup: ({App, props}) => render(App, { props })
    // // Svelte 4
    // setup: ({App, props}) => App.render(props)
}))

It wouldn't make sense to make the API exactly like React / Vue because then we should define a render property that would take care of the rendering and the setup would serve only one purpose and it would be to return App which createInertiaApp already own. Also, the render signature is different between Svelte 4 and 5 and the one from v4 comes from the component itself App.render.

However, I 100% agree this could be simplified to setup(...) only definition. I find it odd to have the same method in SSR/Non-SSR mode that servers 2 different purposes but it seems it's the way it currently is for every adapter so I applied your recommendations.

@reinink

Before I go ahead and update all of my PRs, getting this one merged or approved prior would be great! :) Also, the build is now failing with this error

[vite-plugin-svelte] [plugin vite-plugin-svelte] structuredClone is not defined

I guess the CI setup is simply too old and this is already addressed by https://github.com/inertiajs/inertia/pull/1874