Skip to content

Commit

Permalink
[2.x] Refactor createInertiaApp in Svelte adapter (#2036)
Browse files Browse the repository at this point in the history
* Refactor state management

* Move SSR logic to user-land

* Toggle "removeComments" TS option back to true

* Fix type error
  • Loading branch information
pedroborges authored Oct 18, 2024
1 parent c8228f0 commit a95409f
Show file tree
Hide file tree
Showing 14 changed files with 200 additions and 109 deletions.
52 changes: 43 additions & 9 deletions packages/svelte/src/components/App.svelte
Original file line number Diff line number Diff line change
@@ -1,16 +1,52 @@
<script context="module" lang="ts">
import type { ComponentResolver, ResolvedComponent } from '../types'
import { type Page } from '@inertiajs/core'
export interface InertiaAppProps {
initialComponent: ResolvedComponent
initialPage: Page
resolveComponent: ComponentResolver
}
</script>

<script lang="ts">
import type { LayoutType, LayoutResolver } from '../types'
import type { PageProps } from '@inertiajs/core'
import type { RenderProps } from './Render.svelte'
import Render, { h } from './Render.svelte'
import store, { type InertiaStore } from '../store'
import { router, type PageProps } from '@inertiajs/core'
import Render, { h, type RenderProps } from './Render.svelte'
import { setPage } from '../page'
export let initialComponent: InertiaAppProps['initialComponent']
export let initialPage: InertiaAppProps['initialPage']
export let resolveComponent: InertiaAppProps['resolveComponent']
$: props = resolveProps($store)
let component = initialComponent
let key: number | null = null
let page = initialPage
let renderProps = resolveRenderProps(component, page, key)
setPage(page)
const isServer = typeof window === 'undefined'
if (!isServer) {
router.init({
initialPage,
resolveComponent,
swapComponent: async (args) => {
component = args.component as ResolvedComponent
page = args.page
key = args.preserveState ? key : Date.now()
renderProps = resolveRenderProps(component, page, key)
setPage(page)
},
})
}
/**
* Resolves the render props for the current page component, including layouts.
*/
function resolveProps({ component, page, key = null }: InertiaStore): RenderProps {
function resolveRenderProps(component: ResolvedComponent, page: Page, key: number | null = null): RenderProps {
const child = h(component.default, page.props, [], key)
const layout = component.layout
Expand Down Expand Up @@ -64,6 +100,4 @@
}
</script>

{#if props}
<Render {...props} />
{/if}
<Render {...renderProps} />
2 changes: 1 addition & 1 deletion packages/svelte/src/components/Render.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
props?: PageProps
children?: RenderProps[]
key?: number | null
} | null
}
export type RenderFunction = {
(component: ComponentType, props?: PageProps, children?: RenderProps[], key?: number | null): RenderProps
Expand Down
92 changes: 22 additions & 70 deletions packages/svelte/src/createInertiaApp.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
import { router, setupProgress, type InertiaAppResponse, type Page } from '@inertiajs/core'
import escape from 'html-escape'
import type { ComponentType } from 'svelte'
import { version as SVELTE_VERSION } from 'svelte/package.json'
import App from './components/App.svelte'
import store from './store'
import type { ComponentResolver, ResolvedComponent } from './types'
import App, { type InertiaAppProps } from './components/App.svelte'
import type { ComponentResolver } from './types'

type SvelteRenderResult = { html: string; head: string; css?: { code: string } }
type AppComponent = ComponentType<App> & { render: () => SvelteRenderResult }
type AppComponent = ComponentType<App> & { render: (props: InertiaAppProps) => SvelteRenderResult }

interface CreateInertiaAppProps {
id?: string
resolve: ComponentResolver
setup?: (props: {
el: HTMLElement
App: ComponentType<App>
props: {
initialPage: Page
resolveComponent: ComponentResolver
}
}) => void | App
setup: (props: {
el: HTMLElement | null
App: AppComponent
props: InertiaAppProps
}) => void | App | SvelteRenderResult
progress?:
| false
| {
Expand All @@ -40,75 +35,32 @@ export default async function createInertiaApp({
}: CreateInertiaAppProps): InertiaAppResponse {
const isServer = typeof window === 'undefined'
const el = isServer ? null : document.getElementById(id)
const initialPage: Page = page || JSON.parse(el?.dataset?.page || '{}')
const initialPage: Page = page || JSON.parse(el?.dataset.page || '{}')
const resolveComponent = (name: string) => Promise.resolve(resolve(name))

await Promise.all([resolveComponent(initialPage.component), router.decryptHistory().catch(() => {})]).then(
([initialComponent]) => {
store.set({
component: initialComponent,
page: initialPage,
key: null,
})
},
)
const [initialComponent] = await Promise.all([
resolveComponent(initialPage.component),
router.decryptHistory().catch(() => {}),
])

if (isServer) {
const isSvelte5 = SVELTE_VERSION.startsWith('5')
const { html, head, css } = await (async () => {
if (isSvelte5) {
const { render } = await dynamicImport('svelte/server')
if (typeof render === 'function') {
return render(App) as SvelteRenderResult
}
}
const props: InertiaAppProps = { initialPage, initialComponent, resolveComponent }

return (App as AppComponent).render()
})()
const svelteApp = setup({
el,
App: App as unknown as AppComponent,
props
})

if (isServer) {
const { html, head, css } = svelteApp as SvelteRenderResult

return {
body: `<div data-server-rendered="true" id="${id}" data-page="${escape(JSON.stringify(initialPage))}">${html}</div>`,
head: [head, css ? `<style data-vite-css>${css.code}</style>` : ''],
}
}

if (!el) {
throw new Error(`Element with ID "${id}" not found.`)
}

router.init({
initialPage,
resolveComponent,
swapComponent: async ({ component, page, preserveState }) => {
store.update((current) => ({
component: component as ResolvedComponent,
page,
key: preserveState ? current.key : Date.now(),
}))
},
})

if (progress) {
setupProgress(progress)
}

setup({
el,
App,
props: {
initialPage,
resolveComponent,
},
})
}

// Loads the module dynamically during execution instead of at
// build time. The `@vite-ignore` flag prevents Vite from
// analyzing or pre-bundling this import.
async function dynamicImport(module: string) {
try {
return await import(/* @vite-ignore */ module)
} catch {
return null
}
}
10 changes: 6 additions & 4 deletions packages/svelte/src/page.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { derived } from 'svelte/store'
import store from './store'
import { type Page } from '@inertiajs/core'
import { writable } from 'svelte/store'

const page = derived(store, ($store) => $store.page)
const { set, subscribe } = writable<Page>()

export default page
export const setPage = set

export default { subscribe }
17 changes: 0 additions & 17 deletions packages/svelte/src/store.ts

This file was deleted.

41 changes: 41 additions & 0 deletions packages/svelte/test-app/Pages/Svelte/PropsAndPageStore.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script>
import { inertia, page, router, useForm } from '@inertiajs/svelte'
import { onMount } from 'svelte'
export let foo
const form = useForm({ foo })
console.log('[script] foo prop is', foo)
console.log('[script] $page.props.foo is', $page.props.foo)
$: console.log('[reactive expression] foo prop is', foo)
$: console.log('[reactive expression] $page.props.foo is', $page.props.foo)
onMount(() => {
console.log('[onMount] foo prop is', foo)
console.log('[onMount] $page.props.foo is', $page.props.foo)
})
</script>

<div>
<input id="input" type="text" bind:value={$form.foo}>
<p>foo prop is {foo}</p>
<p>$page.props.foo is {$page.props.foo}</p>

<a
href="/svelte/props-and-page-store"
use:inertia={{ data: { foo: 'bar' } }}
>
Bar
</a>
<a
href="/svelte/props-and-page-store"
use:inertia={{ data: { foo: 'baz' } }}
>
Baz
</a>
<a href="/" use:inertia>
Home
</a>
</div>
4 changes: 2 additions & 2 deletions packages/svelte/test-app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ createInertiaApp({
const pages = import.meta.glob<ResolvedComponent>('./Pages/**/*.svelte', { eager: true })
return pages[`./Pages/${name}.svelte`]
},
setup({ el, App }) {
new App({ target: el })
setup({ el, App, props }) {
new App({ target: el, props })
},
})
2 changes: 1 addition & 1 deletion packages/svelte/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true,
"removeComments": false
"removeComments": true
}
}
4 changes: 2 additions & 2 deletions playgrounds/svelte4/resources/js/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ createInertiaApp({
const pages = import.meta.glob<ResolvedComponent>('./Pages/**/*.svelte', { eager: true })
return pages[`./Pages/${name}.svelte`]
},
setup({ el, App }) {
new App({ target: el, hydrate: true })
setup({ el, App, props }) {
new App({ target: el, props, hydrate: true })
},
})
3 changes: 3 additions & 0 deletions playgrounds/svelte4/resources/js/ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ createServer((page) =>
const pages = import.meta.glob<ResolvedComponent>('./Pages/**/*.svelte', { eager: true })
return pages[`./Pages/${name}.svelte`]
},
setup({ App, props }) {
return App.render(props)
}
}),
)
6 changes: 3 additions & 3 deletions playgrounds/svelte5/resources/js/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ createInertiaApp({
const pages = import.meta.glob<ResolvedComponent>('./Pages/**/*.svelte', { eager: true })
return pages[`./Pages/${name}.svelte`]
},
setup({ el, App }) {
setup({ el, App, props }) {
if (el.dataset.serverRendered === 'true') {
hydrate(App, { target: el })
hydrate(App, { target: el, props })
} else {
mount(App, { target: el })
mount(App, { target: el, props })
}
},
})
4 changes: 4 additions & 0 deletions playgrounds/svelte5/resources/js/ssr.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createInertiaApp, type ResolvedComponent } from '@inertiajs/svelte'
import createServer from '@inertiajs/svelte/server'
import { render } from 'svelte/server'

createServer((page) =>
createInertiaApp({
Expand All @@ -8,5 +9,8 @@ createServer((page) =>
const pages = import.meta.glob<ResolvedComponent>('./Pages/**/*.svelte', { eager: true })
return pages[`./Pages/${name}.svelte`]
},
setup({ App, props }) {
return render(App, { props })
}
}),
)
4 changes: 4 additions & 0 deletions tests/app/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,10 @@ app.get('/deferred-props/page-2', (req, res) => {
}
})

app.get('/svelte/props-and-page-store', (req, res) =>
inertia.render(req, res, { component: 'Svelte/PropsAndPageStore', props: { foo: req.query.foo || 'default' }}),
)

app.all('/sleep', (req, res) => setTimeout(() => res.send(''), 2000))
app.post('/redirect', (req, res) => res.redirect(303, '/dump/get'))
app.get('/location', ({ res }) => inertia.location(res, '/dump/get'))
Expand Down
Loading

0 comments on commit a95409f

Please sign in to comment.