Skip to content

Commit a95409f

Browse files
authored
[2.x] Refactor createInertiaApp in Svelte adapter (#2036)
* Refactor state management * Move SSR logic to user-land * Toggle "removeComments" TS option back to true * Fix type error
1 parent c8228f0 commit a95409f

File tree

14 files changed

+200
-109
lines changed

14 files changed

+200
-109
lines changed

packages/svelte/src/components/App.svelte

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,52 @@
1+
<script context="module" lang="ts">
2+
import type { ComponentResolver, ResolvedComponent } from '../types'
3+
import { type Page } from '@inertiajs/core'
4+
5+
export interface InertiaAppProps {
6+
initialComponent: ResolvedComponent
7+
initialPage: Page
8+
resolveComponent: ComponentResolver
9+
}
10+
</script>
11+
112
<script lang="ts">
213
import type { LayoutType, LayoutResolver } from '../types'
3-
import type { PageProps } from '@inertiajs/core'
4-
import type { RenderProps } from './Render.svelte'
5-
import Render, { h } from './Render.svelte'
6-
import store, { type InertiaStore } from '../store'
14+
import { router, type PageProps } from '@inertiajs/core'
15+
import Render, { h, type RenderProps } from './Render.svelte'
16+
import { setPage } from '../page'
17+
18+
export let initialComponent: InertiaAppProps['initialComponent']
19+
export let initialPage: InertiaAppProps['initialPage']
20+
export let resolveComponent: InertiaAppProps['resolveComponent']
721
8-
$: props = resolveProps($store)
22+
let component = initialComponent
23+
let key: number | null = null
24+
let page = initialPage
25+
let renderProps = resolveRenderProps(component, page, key)
26+
27+
setPage(page)
28+
29+
const isServer = typeof window === 'undefined'
30+
31+
if (!isServer) {
32+
router.init({
33+
initialPage,
34+
resolveComponent,
35+
swapComponent: async (args) => {
36+
component = args.component as ResolvedComponent
37+
page = args.page
38+
key = args.preserveState ? key : Date.now()
39+
40+
renderProps = resolveRenderProps(component, page, key)
41+
setPage(page)
42+
},
43+
})
44+
}
945
1046
/**
1147
* Resolves the render props for the current page component, including layouts.
1248
*/
13-
function resolveProps({ component, page, key = null }: InertiaStore): RenderProps {
49+
function resolveRenderProps(component: ResolvedComponent, page: Page, key: number | null = null): RenderProps {
1450
const child = h(component.default, page.props, [], key)
1551
const layout = component.layout
1652
@@ -64,6 +100,4 @@
64100
}
65101
</script>
66102

67-
{#if props}
68-
<Render {...props} />
69-
{/if}
103+
<Render {...renderProps} />

packages/svelte/src/components/Render.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
props?: PageProps
88
children?: RenderProps[]
99
key?: number | null
10-
} | null
10+
}
1111
1212
export type RenderFunction = {
1313
(component: ComponentType, props?: PageProps, children?: RenderProps[], key?: number | null): RenderProps
Lines changed: 22 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
import { router, setupProgress, type InertiaAppResponse, type Page } from '@inertiajs/core'
22
import escape from 'html-escape'
33
import type { ComponentType } from 'svelte'
4-
import { version as SVELTE_VERSION } from 'svelte/package.json'
5-
import App from './components/App.svelte'
6-
import store from './store'
7-
import type { ComponentResolver, ResolvedComponent } from './types'
4+
import App, { type InertiaAppProps } from './components/App.svelte'
5+
import type { ComponentResolver } from './types'
86

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

1210
interface CreateInertiaAppProps {
1311
id?: string
1412
resolve: ComponentResolver
15-
setup?: (props: {
16-
el: HTMLElement
17-
App: ComponentType<App>
18-
props: {
19-
initialPage: Page
20-
resolveComponent: ComponentResolver
21-
}
22-
}) => void | App
13+
setup: (props: {
14+
el: HTMLElement | null
15+
App: AppComponent
16+
props: InertiaAppProps
17+
}) => void | App | SvelteRenderResult
2318
progress?:
2419
| false
2520
| {
@@ -40,75 +35,32 @@ export default async function createInertiaApp({
4035
}: CreateInertiaAppProps): InertiaAppResponse {
4136
const isServer = typeof window === 'undefined'
4237
const el = isServer ? null : document.getElementById(id)
43-
const initialPage: Page = page || JSON.parse(el?.dataset?.page || '{}')
38+
const initialPage: Page = page || JSON.parse(el?.dataset.page || '{}')
4439
const resolveComponent = (name: string) => Promise.resolve(resolve(name))
4540

46-
await Promise.all([resolveComponent(initialPage.component), router.decryptHistory().catch(() => {})]).then(
47-
([initialComponent]) => {
48-
store.set({
49-
component: initialComponent,
50-
page: initialPage,
51-
key: null,
52-
})
53-
},
54-
)
41+
const [initialComponent] = await Promise.all([
42+
resolveComponent(initialPage.component),
43+
router.decryptHistory().catch(() => {}),
44+
])
5545

56-
if (isServer) {
57-
const isSvelte5 = SVELTE_VERSION.startsWith('5')
58-
const { html, head, css } = await (async () => {
59-
if (isSvelte5) {
60-
const { render } = await dynamicImport('svelte/server')
61-
if (typeof render === 'function') {
62-
return render(App) as SvelteRenderResult
63-
}
64-
}
46+
const props: InertiaAppProps = { initialPage, initialComponent, resolveComponent }
6547

66-
return (App as AppComponent).render()
67-
})()
48+
const svelteApp = setup({
49+
el,
50+
App: App as unknown as AppComponent,
51+
props
52+
})
53+
54+
if (isServer) {
55+
const { html, head, css } = svelteApp as SvelteRenderResult
6856

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

75-
if (!el) {
76-
throw new Error(`Element with ID "${id}" not found.`)
77-
}
78-
79-
router.init({
80-
initialPage,
81-
resolveComponent,
82-
swapComponent: async ({ component, page, preserveState }) => {
83-
store.update((current) => ({
84-
component: component as ResolvedComponent,
85-
page,
86-
key: preserveState ? current.key : Date.now(),
87-
}))
88-
},
89-
})
90-
9163
if (progress) {
9264
setupProgress(progress)
9365
}
94-
95-
setup({
96-
el,
97-
App,
98-
props: {
99-
initialPage,
100-
resolveComponent,
101-
},
102-
})
103-
}
104-
105-
// Loads the module dynamically during execution instead of at
106-
// build time. The `@vite-ignore` flag prevents Vite from
107-
// analyzing or pre-bundling this import.
108-
async function dynamicImport(module: string) {
109-
try {
110-
return await import(/* @vite-ignore */ module)
111-
} catch {
112-
return null
113-
}
11466
}

packages/svelte/src/page.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { derived } from 'svelte/store'
2-
import store from './store'
1+
import { type Page } from '@inertiajs/core'
2+
import { writable } from 'svelte/store'
33

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

6-
export default page
6+
export const setPage = set
7+
8+
export default { subscribe }

packages/svelte/src/store.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<script>
2+
import { inertia, page, router, useForm } from '@inertiajs/svelte'
3+
import { onMount } from 'svelte'
4+
5+
export let foo
6+
7+
const form = useForm({ foo })
8+
9+
console.log('[script] foo prop is', foo)
10+
console.log('[script] $page.props.foo is', $page.props.foo)
11+
12+
$: console.log('[reactive expression] foo prop is', foo)
13+
$: console.log('[reactive expression] $page.props.foo is', $page.props.foo)
14+
15+
onMount(() => {
16+
console.log('[onMount] foo prop is', foo)
17+
console.log('[onMount] $page.props.foo is', $page.props.foo)
18+
})
19+
</script>
20+
21+
<div>
22+
<input id="input" type="text" bind:value={$form.foo}>
23+
<p>foo prop is {foo}</p>
24+
<p>$page.props.foo is {$page.props.foo}</p>
25+
26+
<a
27+
href="/svelte/props-and-page-store"
28+
use:inertia={{ data: { foo: 'bar' } }}
29+
>
30+
Bar
31+
</a>
32+
<a
33+
href="/svelte/props-and-page-store"
34+
use:inertia={{ data: { foo: 'baz' } }}
35+
>
36+
Baz
37+
</a>
38+
<a href="/" use:inertia>
39+
Home
40+
</a>
41+
</div>

packages/svelte/test-app/app.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ createInertiaApp({
88
const pages = import.meta.glob<ResolvedComponent>('./Pages/**/*.svelte', { eager: true })
99
return pages[`./Pages/${name}.svelte`]
1010
},
11-
setup({ el, App }) {
12-
new App({ target: el })
11+
setup({ el, App, props }) {
12+
new App({ target: el, props })
1313
},
1414
})

packages/svelte/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@
2020
"noUnusedLocals": true,
2121
"noUnusedParameters": true,
2222
"preserveConstEnums": true,
23-
"removeComments": false
23+
"removeComments": true
2424
}
2525
}

playgrounds/svelte4/resources/js/app.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ createInertiaApp({
55
const pages = import.meta.glob<ResolvedComponent>('./Pages/**/*.svelte', { eager: true })
66
return pages[`./Pages/${name}.svelte`]
77
},
8-
setup({ el, App }) {
9-
new App({ target: el, hydrate: true })
8+
setup({ el, App, props }) {
9+
new App({ target: el, props, hydrate: true })
1010
},
1111
})

playgrounds/svelte4/resources/js/ssr.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@ createServer((page) =>
88
const pages = import.meta.glob<ResolvedComponent>('./Pages/**/*.svelte', { eager: true })
99
return pages[`./Pages/${name}.svelte`]
1010
},
11+
setup({ App, props }) {
12+
return App.render(props)
13+
}
1114
}),
1215
)

0 commit comments

Comments
 (0)