Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
8929a9f
feat(svelte5): add Svelte 5 adapter and test app
brodienguyen Sep 23, 2025
f4a305c
refactor(test-app): update imports to @inertiajs/svelte5
brodienguyen Sep 23, 2025
2cddcf5
refactor(test-app): update to svelte5 page store usage
brodienguyen Sep 23, 2025
0a4ab02
refactor(components): migrate to $props and $state
brodienguyen Sep 23, 2025
2c77cc9
refactor(components): switch to children/fallback props
brodienguyen Sep 23, 2025
9fd8b75
refactor(deferred-props): update fallback syntax
brodienguyen Sep 23, 2025
05f05d8
refactor(events): use $page.url instead of page.url
brodienguyen Sep 23, 2025
6815b1b
refactor(FormComponent): use snippet for children
brodienguyen Sep 23, 2025
6b346fc
refactor(Deferred): improve reactivity in effect
brodienguyen Sep 23, 2025
d12b857
refactor(App): improve page state reactivity
brodienguyen Sep 23, 2025
fd1f177
refactor(FormComponent): wrap form content in snippet
brodienguyen Sep 23, 2025
ec49202
refactor(components): update event handler syntax
brodienguyen Sep 23, 2025
c891424
feat(form): expose isDirty and setError in Form
brodienguyen Sep 23, 2025
f353ab4
refactor(form): update Form usage to snippet block
brodienguyen Sep 23, 2025
b322570
refactor(form): update Form usage to snippet block
brodienguyen Sep 23, 2025
232103a
refactor(FormComponent): update slot usage
brodienguyen Sep 23, 2025
acf6066
refactor(form): update Form usage and error display
brodienguyen Sep 23, 2025
9e911e1
refactor(FormComponent): update Form usage
brodienguyen Sep 23, 2025
935b917
feat(FormComponent): add email field to form
brodienguyen Sep 23, 2025
8337de1
test(svelte): skip tests for svelte5 adapter
brodienguyen Sep 23, 2025
073f47f
refactor(form): migrate useForm to Svelte runes
brodienguyen Sep 23, 2025
9906982
refactor(FormHelper): remove $ prefix from form usage
brodienguyen Sep 24, 2025
461cffc
refactor(FormHelper): remove $ prefix from form usage
brodienguyen Sep 24, 2025
e1bf42a
refactor(FormComponent): update Reset.svelte children
brodienguyen Sep 24, 2025
adb6a9b
refactor(FormComponent): use snippet for form children
brodienguyen Sep 24, 2025
0d719a1
refactor(form): use snippet for form children
brodienguyen Sep 24, 2025
fef4783
fix(useForm): correct error handling in onError callback
brodienguyen Sep 24, 2025
fea83cf
fix(useForm): improve reactivity and reset logic
brodienguyen Sep 24, 2025
2901ea6
fix(form): add id to error message div
brodienguyen Sep 24, 2025
f39a609
refactor(useForm): improve defaults update logic
brodienguyen Sep 24, 2025
bf61286
refactor(test-app): make window props reactive to page
brodienguyen Sep 24, 2025
874815a
refactor(test-app): use $props in Svelte pages
brodienguyen Sep 24, 2025
664c038
refactor(test-app): use $props in Svelte5 components
brodienguyen Sep 24, 2025
e5ed818
feat(useForm): add reactive getters for form data
brodienguyen Sep 24, 2025
bc59e37
refactor(remember): remove $ prefix from form references
brodienguyen Sep 24, 2025
b684a14
refactor(ManyGroups): use $props for prop destructuring
brodienguyen Sep 24, 2025
ae6817c
refactor(InstantReload): use $props for foo and bar
brodienguyen Sep 24, 2025
57c25d7
refactor(test-app): use $props destructuring in pages
brodienguyen Sep 24, 2025
cdec571
refactor(form-helper): remove $ prefix from form usage
brodienguyen Sep 24, 2025
0919055
refactor(WhenVisible): use #snippet blocks for slots
brodienguyen Sep 24, 2025
5969cd1
refactor(test-app): use $props in Svelte components
brodienguyen Sep 24, 2025
af3cf0f
refactor(FormHelper): update form type to InertiaFormRunes
brodienguyen Sep 24, 2025
351c05c
refactor(FormHelper): update to use InertiaFormRunes type
brodienguyen Sep 24, 2025
720744c
refactor(DeepMergeProps): use $props and $state helpers
brodienguyen Sep 24, 2025
a351d97
fix(useForm): update defaults and remember logic
brodienguyen Sep 24, 2025
0904ca9
refactor(useForm): improve Svelte 5 runes compatibility
brodienguyen Sep 24, 2025
a897d2e
test: skip tests for svelte5 package
brodienguyen Sep 24, 2025
fdc07f0
refactor(useForm): simplify and modernize form logic
brodienguyen Sep 24, 2025
cb60fff
refactor(form): migrate useForm to TypeScript store
brodienguyen Sep 24, 2025
da1be08
refactor(FormComponent): update error markup and layout
brodienguyen Sep 24, 2025
ef1bf65
refactor(svelte5): migrate usePrefetch to $state
brodienguyen Sep 24, 2025
1c8dfe1
refactor(svelte5): rewrite useRemember for Svelte 5
brodienguyen Sep 24, 2025
9bc6a48
fix(index): update imports for usePrefetch and useRemember
brodienguyen Sep 24, 2025
eed53a9
build(test-app): update Svelte and Vite dependencies
brodienguyen Sep 24, 2025
1a0cb87
lint: run pnpm run format
brodienguyen Sep 24, 2025
6ba2754
ci(workflows): add svelte5 to adapter matrix
brodienguyen Sep 25, 2025
a0107b9
Merge remote-tracking branch 'inertia/master' into new-package-svelte-5
puRe1337 Oct 31, 2025
ccc3d85
chore: update lock file
puRe1337 Oct 31, 2025
880260c
chore: update dependencies
puRe1337 Oct 31, 2025
353e03c
chore: update test-app packages and copied files from original svelte…
puRe1337 Oct 31, 2025
bd39ed2
refactor: migrate Render.svelte to Component and use RenderProps type…
puRe1337 Oct 31, 2025
86dc955
feat: purge page store completely and use state only
puRe1337 Oct 31, 2025
14bcd0a
refactor: use Component instead of ComponentType
puRe1337 Oct 31, 2025
d1c0f05
feat: migrated useForm to runes mode
puRe1337 Oct 31, 2025
9fc163a
chore: keep types in svelte5/index.ts of original package
puRe1337 Oct 31, 2025
afe3870
chore: types inside svelte/5links.ts and more syncing with current sv…
puRe1337 Oct 31, 2025
437cc90
fix: pass down a snapshot of current state to ensure core router can …
puRe1337 Oct 31, 2025
8778403
refactor: tidy up createInertiaApp
puRe1337 Oct 31, 2025
a374f8a
fix: dedupe axios version in lock file
puRe1337 Oct 31, 2025
b4ff8ea
fix: skip more test
puRe1337 Oct 31, 2025
43ae195
fix: App.svelte effect
puRe1337 Oct 31, 2025
22ead7e
refactor: tidy up Form.svelte
puRe1337 Oct 31, 2025
13a064d
feat: add InfiniteScroll but in runes style
puRe1337 Oct 31, 2025
f48fd8d
refactor: tidy up Link, WhenVisible
puRe1337 Oct 31, 2025
44f3cb3
chore: run sv migrate on test-app
puRe1337 Oct 31, 2025
c421a8a
refactor: tidy up WhenVisible component
puRe1337 Oct 31, 2025
7309b45
refactor: types in Deferred component & fix side-effects
puRe1337 Oct 31, 2025
9d1f527
refactor: dont take a snapshot of newPage inside page state
puRe1337 Oct 31, 2025
3ae5d95
refactor: tidyup PreseveEqualProps
puRe1337 Oct 31, 2025
8865267
chore: add missing Router component
puRe1337 Oct 31, 2025
2e3d86d
chore: add comment to resolveRenderProps
puRe1337 Oct 31, 2025
172ef40
chore: add type-check:test-app:svelte5 script to package.json
puRe1337 Oct 31, 2025
3d58470
fix: dont return mount
puRe1337 Oct 31, 2025
5ff46d3
chore: disable eslint on children in Grid test component
puRe1337 Oct 31, 2025
197ffac
fix: type errors
puRe1337 Oct 31, 2025
87e7a4b
format code-style
puRe1337 Oct 31, 2025
bca56f3
refactor: use attachment instead of manually handling
puRe1337 Oct 31, 2025
f031658
fix: set target version to ES2020 in build
puRe1337 Nov 1, 2025
f7255f2
fix(ci): prevent pnpm wildcard from matching both svelte and svelte5 …
puRe1337 Nov 3, 2025
900e72a
fix(ci): handle vue3 build in playwright workflow
puRe1337 Nov 3, 2025
a7a3fc5
fix: await setup in createInertiaApp
puRe1337 Nov 4, 2025
61f8b13
fix: improve nestedA comparison logic for preserveEqualProps
puRe1337 Nov 4, 2025
30f2814
feat: add missing 'dev:test-app:svelte5' script
puRe1337 Nov 4, 2025
26a0c70
feat: add lint script for svelte5 test app
puRe1337 Nov 4, 2025
1986f60
refactor: use types instead of any in PreserveEqualProps
puRe1337 Nov 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/es2020-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
adapter: ['core', 'react', 'vue', 'svelte']
adapter: ['core', 'react', 'vue', 'svelte', 'svelte5']

steps:
- name: Checkout code
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-24.04
strategy:
matrix:
adapter: ['vue', 'react', 'svelte']
adapter: ['vue', 'react', 'svelte', 'svelte5']
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -27,7 +27,7 @@ jobs:
run: pnpm install

- name: Build Inertia
run: pnpm -r --filter ./packages/core --filter ./packages/${{ matrix.adapter }}* build
run: pnpm -r --filter ./packages/core --filter "@inertiajs/${{ matrix.adapter == 'vue' && 'vue3' || matrix.adapter }}" build

- name: Type-check test-app
run: cd packages/${{ matrix.adapter == 'vue' && 'vue3' || matrix.adapter }}/test-app && pnpm run type-check
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
adapter: ['core', 'vue3', 'react', 'svelte']
adapter: ['core', 'vue3', 'react', 'svelte', 'svelte5']
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ inertia/
│ │ └── test-app/ React test application
│ ├── svelte/ Svelte adapter
│ │ └── test-app/ Svelte test application
│ ├── svelte5/ Svelte 5 adapter
│ │ └── test-app/ Svelte 5 test application
│ └── vue3/ Vue 3 adapter
│ └── test-app/ Vue 3 test application
├── playgrounds/ Full Laravel applications for manual testing
Expand Down Expand Up @@ -72,6 +74,7 @@ Run the test suite for a specific adapter:
```sh
pnpm test:react
pnpm test:svelte
pnpm test:svelte5
pnpm test:vue
```

Expand Down Expand Up @@ -109,6 +112,7 @@ All adapters use the same Node.js backend and Playwright test suite. The only di
tests/app/server.js Shared Node.js backend
├── serves: react test app (when PACKAGE=react)
├── serves: svelte test app (when PACKAGE=svelte)
├── serves: svelte5 test app (when PACKAGE=svelte5)
└── serves: vue test app (when PACKAGE=vue3)

tests/*.spec.ts Shared Playwright test suite
Expand All @@ -120,6 +124,7 @@ When running a test command, the correct adapter is selected automatically:
| ------- | --------------- | ---------------- | -------------------------------------------------- |
| React | `react` | 13716 | [http://localhost:13716/](http://localhost:13716/) |
| Svelte | `svelte` | 13717 | [http://localhost:13717/](http://localhost:13717/) |
| Svelte5 | `svelte5` | 13718 | [http://localhost:13718/](http://localhost:13718/) |
| Vue 3 | `vue3` | 13715 | [http://localhost:13715/](http://localhost:13715/) |

### Automatic Test Server Boot
Expand All @@ -141,6 +146,7 @@ Or start an individual one:
```sh
pnpm dev:test-app:react
pnpm dev:test-app:svelte
pnpm dev:test-app:svelte5
pnpm dev:test-app:vue
```

Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,22 @@
"dev:test-app": "pnpx concurrently -c \"#61dafb,#d43008,#32a06f\" \"pnpm dev:test-app:react\" \"pnpm dev:test-app:svelte\" \"pnpm dev:test-app:vue\" --names=react,svelte,vue",
"dev:test-app:react": "pnpx concurrently -c \"#c4b5fd,#ffa800\" \"cd tests/app && PACKAGE=react pnpm serve:watch\" \"cd packages/react/test-app && pnpm run dev\" --names=server,vite",
"dev:test-app:svelte": "pnpx concurrently -c \"#c4b5fd,#ffa800\" \"cd tests/app && PACKAGE=svelte pnpm serve:watch\" \"cd packages/svelte/test-app && pnpm run dev\" --names=server,vite",
"dev:test-app:svelte5": "pnpx concurrently -c \"#c4b5fd,#ffa800\" \"cd tests/app && PACKAGE=svelte5 pnpm serve:watch\" \"cd packages/svelte5/test-app && pnpm run dev\" --names=server,vite",
"dev:test-app:vue": "pnpx concurrently -c \"#c4b5fd,#ffa800\" \"cd tests/app && PACKAGE=vue3 pnpm serve:watch\" \"cd packages/vue3/test-app && pnpm run dev\" --names=server,vite",
"es2020-check": "pnpm -r --filter './packages/*' es2020-check",
"lint:test-app": "pnpm -r --filter './packages/*/test-app' lint",
"lint:test-app:react": "cd packages/react/test-app && pnpm run lint",
"lint:test-app:svelte": "cd packages/svelte/test-app && pnpm run lint",
"lint:test-app:svelte5": "cd packages/svelte5/test-app && pnpm run lint",
"lint:test-app:vue": "cd packages/vue3/test-app && pnpm run lint",
"type-check:test-app": "pnpm -r --filter './packages/*/test-app' type-check",
"type-check:test-app:react": "cd packages/react/test-app && pnpm run type-check",
"type-check:test-app:svelte": "cd packages/svelte/test-app && pnpm run type-check",
"type-check:test-app:svelte5": "cd packages/svelte5/test-app && pnpm run type-check",
"type-check:test-app:vue": "cd packages/vue3/test-app && pnpm run type-check",
"test:react": "PACKAGE=react playwright test",
"test:svelte": "PACKAGE=svelte playwright test",
"test:svelte5": "PACKAGE=svelte5 playwright test",
"test:vue": "PACKAGE=vue3 playwright test",
"playground:react": "cd playgrounds/react && ./init.sh && composer run dev",
"playground:svelte4": "cd playgrounds/svelte4 && ./init.sh && composer run dev",
Expand Down
6 changes: 6 additions & 0 deletions packages/svelte5/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dist
types
node_modules
package-lock.json
yarn.lock
.svelte-kit
21 changes: 21 additions & 0 deletions packages/svelte5/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) Jonathan Reinink <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
69 changes: 69 additions & 0 deletions packages/svelte5/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"name": "@inertiajs/svelte5",
"version": "2.2.14",
"license": "MIT",
"description": "The Svelte 5 adapter for Inertia.js",
"contributors": [
"Jonathan Reinink <[email protected]>",
"Pedro Borges <[email protected]>"
],
"homepage": "https://inertiajs.com/",
"repository": {
"type": "git",
"url": "https://github.com/inertiajs/inertia.git",
"directory": "packages/svelte5"
},
"bugs": {
"url": "https://github.com/inertiajs/inertia/issues"
},
"files": [
"dist",
"!dist/**/*.test.*",
"!dist/**/*.spec.*"
],
"type": "module",
"types": "./dist/index.d.ts",
"svelte": "./dist/index.js",
"exports": {
".": {
"types": "./dist/index.d.ts",
"svelte": "./dist/index.js"
},
"./server": {
"types": "./dist/server.d.ts",
"svelte": "./dist/server.js"
}
},
"scripts": {
"build": "pnpm package && svelte-check --tsconfig ./tsconfig.json && publint",
"build:with-deps": "svelte-kit sync && vite build --config vite-with-deps.config.js",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"dev": "pnpm package --watch",
"es2020-check": "pnpm build:with-deps && es-check es2020 \"dist/**/*.js\" --checkFeatures --module --noCache --verbose",
"package": "svelte-kit sync && svelte-package --input src",
"prepublishOnly": "pnpm build"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/kit": "^2.48.4",
"@sveltejs/package": "^2.5.4",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"axios": "^1.13.1",
"es-check": "^9.4.4",
"publint": "^0.3.15",
"svelte": "^5.43.2",
"svelte-check": "^4.3.3",
"tslib": "^2.8.1",
"typescript": "^5.9.3",
"vite": "^7.1.12"
},
"peerDependencies": {
"svelte": "^5.0.0"
},
"dependencies": {
"@inertiajs/core": "workspace:*",
"@types/lodash-es": "^4.17.12",
"lodash-es": "^4.17.21"
}
}
3 changes: 3 additions & 0 deletions packages/svelte5/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Inertia.js Svelte Adapter

Visit [inertiajs.com](https://inertiajs.com/) to learn more.
109 changes: 109 additions & 0 deletions packages/svelte5/src/components/App.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<script module lang="ts">
import type { ComponentResolver, ResolvedComponent } from '../types'
import { type Page, type PageProps } from '@inertiajs/core'

export interface InertiaAppProps<SharedProps extends PageProps = PageProps> {
initialComponent: ResolvedComponent
initialPage: Page<SharedProps>
resolveComponent: ComponentResolver
}
</script>

<script lang="ts">
import type { LayoutType, LayoutResolver } from '../types'
import { router } from '@inertiajs/core'
import Render, { h, type RenderProps } from './Render.svelte'
import { setPage } from '../page.svelte'

interface Props {
initialComponent: InertiaAppProps['initialComponent']
initialPage: InertiaAppProps['initialPage']
resolveComponent: InertiaAppProps['resolveComponent']
}

const { initialComponent, initialPage, resolveComponent }: Props = $props()

let component = $state(initialComponent)
let key = $state<number | null>(null)
let page = $state(initialPage)
let renderProps = $derived.by<RenderProps>(() => resolveRenderProps(component, page, key))

// Reactively update the global page state when local page state changes
$effect.pre(() => {
setPage(page)
})

const isServer = typeof window === 'undefined'

if (!isServer) {
router.init<ResolvedComponent>({
initialPage,
resolveComponent,
swapComponent: async (args) => {
component = args.component
page = args.page

key = args.preserveState ? key : Date.now()
},
})
}

/**
* Resolves the render props for the current page component, including layouts.
*/
function resolveRenderProps(component: ResolvedComponent, page: Page, key: number | null = null): RenderProps {
// If the component does not exists, it will throw on component.default and component.layout here
const child = h(component.default, page.props, [], key)
const layout = component.layout

return layout ? resolveLayout(layout, child, page.props, key) : child
}

/**
* Builds the nested layout structure by wrapping the child component with the provided layouts.
*
* Resulting nested structure:
*
* {
* "component": OuterLayout,
* "key": 123456,
* "children": [{
* "component": InnerLayout,
* "key": 123456,
* "children": [{
* "component": PageComponent,
* "key": 123456,
* "children": [],
* }],
* }],
* }
*/
function resolveLayout(
layout: LayoutType,
child: RenderProps,
pageProps: PageProps,
key: number | null,
): RenderProps {
if (isLayoutFunction(layout)) {
return layout(h, child)
}

if (Array.isArray(layout)) {
return layout
.slice()
.reverse()
.reduce((currentRender, layoutComponent) => h(layoutComponent, pageProps, [currentRender], key), child)
}

return h(layout, pageProps, child ? [child] : [], key)
}

/**
* Type guard to check if layout is a LayoutResolver
*/
function isLayoutFunction(layout: LayoutType): layout is LayoutResolver {
return typeof layout === 'function' && layout.length === 2 && typeof layout.prototype === 'undefined'
}
</script>

<Render {...renderProps} />
42 changes: 42 additions & 0 deletions packages/svelte5/src/components/Deferred.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<script lang="ts">
import { untrack } from 'svelte'
import { page } from '../index'

interface Props {
data: string | string[]
fallback?: import('svelte').Snippet
children?: import('svelte').Snippet
}

let { data, fallback, children }: Props = $props()

const keys = Array.isArray(data) ? data : [data]
let loaded = $state(false)

const isServer = typeof window === 'undefined'
if (!isServer) {
// Use $effect to watch for changes in pageState.props
$effect(() => {
// Access pageState.props to make this effect reactive
const props = page.props

// Wrap this up into untrack, to make sure it doesn't gets picked as a depedency to retrigger the $effect
untrack(() => {
// Ensures the content isn't loaded before the deferred props are available
window.queueMicrotask(() => {
loaded = keys.every((key) => typeof props[key] !== 'undefined')
})
})
})
}

if (!fallback) {
throw new Error('`<Deferred>` requires a `fallback` snippet')
}
</script>

{#if loaded}
{@render children?.()}
{:else}
{@render fallback?.()}
{/if}
Loading