diff --git a/examples/todomvc/.gitignore b/examples/todomvc/.gitignore
new file mode 100644
index 0000000..6635cf5
--- /dev/null
+++ b/examples/todomvc/.gitignore
@@ -0,0 +1,10 @@
+.DS_Store
+node_modules
+/build
+/.svelte-kit
+/package
+.env
+.env.*
+!.env.example
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
diff --git a/examples/todomvc/.npmrc b/examples/todomvc/.npmrc
new file mode 100644
index 0000000..b6f27f1
--- /dev/null
+++ b/examples/todomvc/.npmrc
@@ -0,0 +1 @@
+engine-strict=true
diff --git a/examples/todomvc/.prettierignore b/examples/todomvc/.prettierignore
new file mode 100644
index 0000000..3897265
--- /dev/null
+++ b/examples/todomvc/.prettierignore
@@ -0,0 +1,13 @@
+.DS_Store
+node_modules
+/build
+/.svelte-kit
+/package
+.env
+.env.*
+!.env.example
+
+# Ignore files for PNPM, NPM and YARN
+pnpm-lock.yaml
+package-lock.json
+yarn.lock
diff --git a/examples/todomvc/.prettierrc b/examples/todomvc/.prettierrc
new file mode 100644
index 0000000..a77fdde
--- /dev/null
+++ b/examples/todomvc/.prettierrc
@@ -0,0 +1,9 @@
+{
+ "useTabs": true,
+ "singleQuote": true,
+ "trailingComma": "none",
+ "printWidth": 100,
+ "plugins": ["prettier-plugin-svelte"],
+ "pluginSearchDirs": ["."],
+ "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
+}
diff --git a/examples/todomvc/README.md b/examples/todomvc/README.md
new file mode 100644
index 0000000..6e06126
--- /dev/null
+++ b/examples/todomvc/README.md
@@ -0,0 +1,26 @@
+# Todo MVC
+
+An example implementation of the Todo MVC app. It uses SvelteKit's [format actions](https://kit.svelte.dev/docs/form-actions). It uses progressive enhancement, which means the app is still functional without JavaScript - but when it _is_ available, it provides a nicer experience by having optimistic UI updates, for example showing the new TODO item before it actually exists in the database.
+
+## Developing
+
+Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
+
+```bash
+npm run dev
+
+# or start the server and open the app in a new browser tab
+npm run dev -- --open
+```
+
+## Building
+
+To create a production version of your app:
+
+```bash
+npm run build
+```
+
+You can preview the production build with `npm run preview`.
+
+> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
diff --git a/examples/todomvc/package.json b/examples/todomvc/package.json
new file mode 100644
index 0000000..4ba3639
--- /dev/null
+++ b/examples/todomvc/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "todomvc",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "dev": "vite dev",
+ "build": "vite build",
+ "preview": "vite preview",
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
+ "lint": "prettier --plugin-search-dir . --check .",
+ "format": "prettier --plugin-search-dir . --write ."
+ },
+ "devDependencies": {
+ "@sveltejs/adapter-auto": "^2.0.0",
+ "@sveltejs/kit": "^1.12.0",
+ "@sveltejs/package": "^2.0.0",
+ "prettier": "^2.8.0",
+ "prettier-plugin-svelte": "^2.8.1",
+ "publint": "^0.1.9",
+ "svelte": "^3.57.0",
+ "svelte-check": "^3.0.1",
+ "tslib": "^2.4.1",
+ "typescript": "^4.9.3",
+ "vite": "^4.0.0"
+ },
+ "type": "module"
+}
diff --git a/examples/todomvc/src/app.d.ts b/examples/todomvc/src/app.d.ts
new file mode 100644
index 0000000..26a9569
--- /dev/null
+++ b/examples/todomvc/src/app.d.ts
@@ -0,0 +1,9 @@
+// See https://kit.svelte.dev/docs/types#app
+// for information about these interfaces
+// and what to do when importing types
+declare namespace App {
+ // interface Error {}
+ // interface Locals {}
+ // interface PageData {}
+ // interface Platform {}
+}
diff --git a/examples/todomvc/src/app.html b/examples/todomvc/src/app.html
new file mode 100644
index 0000000..effe0d0
--- /dev/null
+++ b/examples/todomvc/src/app.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ %sveltekit.head%
+
+
+ %sveltekit.body%
+
+
diff --git a/examples/todomvc/src/routes/+page.server.ts b/examples/todomvc/src/routes/+page.server.ts
new file mode 100644
index 0000000..d405de8
--- /dev/null
+++ b/examples/todomvc/src/routes/+page.server.ts
@@ -0,0 +1,56 @@
+import type { Actions, PageServerLoad } from './$types';
+import {
+ addTodo,
+ clearCompleted,
+ deleteTodo,
+ editTodo,
+ getTodos,
+ toggleAll,
+ toggleTodo
+} from './db.server';
+
+const wait = async () => {
+ return new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
+};
+
+export const load = (() => {
+ return { todos: getTodos() };
+}) satisfies PageServerLoad;
+
+export const actions = {
+ addTodo: async ({ request }) => {
+ await wait();
+ const form = await request.formData();
+ const title = form.get('title') as string;
+ addTodo(title);
+ },
+ deleteTodo: async ({ request }) => {
+ await wait();
+ const form = await request.formData();
+ const id = form.get('id') as string;
+ deleteTodo(id);
+ },
+ editTodo: async ({ request }) => {
+ await wait();
+ const form = await request.formData();
+ const id = form.get('id') as string;
+ const title = form.get('title') as string;
+ editTodo(id, title);
+ },
+ toggleTodo: async ({ request }) => {
+ await wait();
+ const form = await request.formData();
+ const id = form.get('id') as string;
+ toggleTodo(id);
+ },
+ toggleAll: async ({ request }) => {
+ await wait();
+ const form = await request.formData();
+ const completed = form.get('completed') === 'true';
+ toggleAll(completed);
+ },
+ clearCompleted: async () => {
+ await wait();
+ clearCompleted();
+ }
+} satisfies Actions;
diff --git a/examples/todomvc/src/routes/+page.svelte b/examples/todomvc/src/routes/+page.svelte
new file mode 100644
index 0000000..6f29c35
--- /dev/null
+++ b/examples/todomvc/src/routes/+page.svelte
@@ -0,0 +1,182 @@
+
+
+
+ Todos
+
+
+
+
+
+
+ {#if todos.length > 0}
+
+
+
+
+ {#each filtered as todo (todo.id + (pending(todo) ? 'pending' : ''))}
+ -
+
+
+
+
+
+
+ {/each}
+
+
+
+
+ {/if}
+
diff --git a/examples/todomvc/src/routes/db.server.ts b/examples/todomvc/src/routes/db.server.ts
new file mode 100644
index 0000000..0cb2e56
--- /dev/null
+++ b/examples/todomvc/src/routes/db.server.ts
@@ -0,0 +1,44 @@
+// Todos are managed managed in memory, so we don't need a database. We can't deploy this
+// to the edge/serverless environments for that reason, but it's fine for local development.
+
+export interface Todo {
+ id: string;
+ title: string;
+ completed: boolean;
+}
+
+let todos: Todo[] = [];
+
+export function getTodos() {
+ return todos;
+}
+
+export function addTodo(title: string) {
+ todos.push({ id: 'id-' + Math.random(), title, completed: false });
+}
+
+export function deleteTodo(id: string) {
+ todos = todos.filter((todo) => todo.id !== id);
+}
+
+export function editTodo(id: string, title: string) {
+ const todo = todos.find((todo) => todo.id === id);
+ if (todo) {
+ todo.title = title;
+ }
+}
+
+export function toggleTodo(id: string) {
+ const todo = todos.find((todo) => todo.id === id);
+ if (todo) {
+ todo.completed = !todo.completed;
+ }
+}
+
+export function toggleAll(completed: boolean) {
+ todos.forEach((todo) => (todo.completed = completed));
+}
+
+export function clearCompleted() {
+ todos = todos.filter((todo) => !todo.completed);
+}
diff --git a/examples/todomvc/src/styles.css b/examples/todomvc/src/styles.css
new file mode 100644
index 0000000..ef31997
--- /dev/null
+++ b/examples/todomvc/src/styles.css
@@ -0,0 +1,334 @@
+/* The original TODO-MVC CSS, minimally adjusted to enable progressive enhancement */
+
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+button {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+ font-size: 100%;
+ vertical-align: baseline;
+ font-family: inherit;
+ font-weight: inherit;
+ color: inherit;
+ -webkit-appearance: none;
+ appearance: none;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+ font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ line-height: 1.4em;
+ background: #f5f5f5;
+ color: #4d4d4d;
+ min-width: 230px;
+ max-width: 550px;
+ margin: 0 auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ font-weight: 300;
+}
+
+:focus {
+ outline: 0;
+}
+
+.hidden {
+ display: none;
+}
+
+.todoapp {
+ background: #fff;
+ margin: 130px 0 40px 0;
+ position: relative;
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
+}
+
+.todoapp input::-webkit-input-placeholder {
+ font-style: italic;
+ font-weight: 300;
+ color: #e6e6e6;
+}
+
+.todoapp input::-moz-placeholder {
+ font-style: italic;
+ font-weight: 300;
+ color: #e6e6e6;
+}
+
+.todoapp input::input-placeholder {
+ font-style: italic;
+ font-weight: 300;
+ color: #e6e6e6;
+}
+
+.todoapp h1 {
+ position: absolute;
+ top: -155px;
+ width: 100%;
+ font-size: 100px;
+ font-weight: 100;
+ text-align: center;
+ color: rgba(175, 47, 47, 0.15);
+ -webkit-text-rendering: optimizeLegibility;
+ -moz-text-rendering: optimizeLegibility;
+ text-rendering: optimizeLegibility;
+}
+
+.new-todo,
+.edit {
+ position: relative;
+ margin: 0;
+ width: 100%;
+ font-size: 24px;
+ font-family: inherit;
+ font-weight: inherit;
+ line-height: 1.4em;
+ border: 0;
+ color: inherit;
+ padding: 6px;
+ box-sizing: border-box;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.new-todo {
+ padding: 16px 16px 16px 60px;
+ border: none;
+ background: rgba(0, 0, 0, 0.003);
+ box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
+}
+
+.edit {
+ padding: 12px 16px;
+}
+
+.main {
+ position: relative;
+ z-index: 2;
+ border-top: 1px solid #e6e6e6;
+}
+
+.toggle-all {
+ width: 60px;
+ height: 34px;
+ font-size: 0;
+ position: absolute;
+ top: -52px;
+ left: -5px;
+ -webkit-transform: rotate(90deg);
+ transform: rotate(90deg);
+}
+
+.toggle-all:before {
+ content: '❯';
+ font-size: 22px;
+ color: #e6e6e6;
+ padding: 10px 27px 10px 27px;
+}
+
+.toggle-all.checked:before {
+ color: #737373;
+}
+
+.todo-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.todo-list li {
+ position: relative;
+ font-size: 24px;
+ border-bottom: 1px solid #ededed;
+ display: flex;
+ align-items: center;
+ height: 58px;
+}
+
+.todo-list li:last-child {
+ border-bottom: none;
+}
+
+.todo-list li.pending {
+ opacity: 0.5;
+}
+
+.todo-list li .text {
+ height: 58px;
+ flex: 100% 1 1;
+}
+
+.todo-list li .toggle {
+ text-align: center;
+ width: 40px;
+ height: 40px;
+ margin: auto 0;
+ border: none; /* Mobile Safari */
+ -webkit-appearance: none;
+ appearance: none;
+}
+
+.todo-list li .toggle {
+ /*
+ Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
+ IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
+ */
+ background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
+ background-repeat: no-repeat;
+ background-position: center left;
+}
+
+.todo-list li.completed .toggle {
+ background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
+}
+
+.todo-list li label {
+ word-break: break-all;
+ padding: 15px 15px 15px 60px;
+ display: block;
+ line-height: 1.2;
+ transition: color 0.4s;
+}
+
+.todo-list li.completed .edit {
+ color: #d9d9d9;
+ text-decoration: line-through;
+}
+
+.todo-list li .destroy {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 10px;
+ bottom: 0;
+ width: 40px;
+ height: 40px;
+ margin: auto 0;
+ font-size: 30px;
+ color: #cc9a9a;
+ margin-bottom: 11px;
+ transition: color 0.2s ease-out;
+}
+
+.todo-list li .destroy:hover {
+ color: #af5b5e;
+}
+
+.todo-list li .destroy:after {
+ content: '×';
+}
+
+.todo-list li:hover .destroy {
+ display: block;
+}
+
+.footer {
+ color: #777;
+ padding: 10px 15px;
+ height: 20px;
+ text-align: center;
+ border-top: 1px solid #e6e6e6;
+}
+
+.footer:before {
+ content: '';
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ height: 50px;
+ overflow: hidden;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0, 0, 0, 0.2),
+ 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0, 0, 0, 0.2);
+}
+
+.todo-count {
+ float: left;
+ text-align: left;
+}
+
+.todo-count strong {
+ font-weight: 300;
+}
+
+.filters {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ position: absolute;
+ right: 0;
+ left: 0;
+}
+
+.filters li {
+ display: inline;
+}
+
+.filters li a {
+ color: inherit;
+ margin: 3px;
+ padding: 3px 7px;
+ text-decoration: none;
+ border: 1px solid transparent;
+ border-radius: 3px;
+}
+
+.filters li a:hover {
+ border-color: rgba(175, 47, 47, 0.1);
+}
+
+.filters li a.selected {
+ border-color: rgba(175, 47, 47, 0.2);
+}
+
+.clear-completed,
+html .clear-completed:active {
+ float: right;
+ position: relative;
+ line-height: 20px;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.clear-completed:hover {
+ text-decoration: underline;
+}
+
+.info {
+ margin: 65px auto 0;
+ color: #bfbfbf;
+ font-size: 10px;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ text-align: center;
+}
+
+.info p {
+ line-height: 1;
+}
+
+.info a {
+ color: inherit;
+ text-decoration: none;
+ font-weight: 400;
+}
+
+.info a:hover {
+ text-decoration: underline;
+}
+
+@media (max-width: 430px) {
+ .footer {
+ height: 50px;
+ }
+
+ .filters {
+ bottom: 10px;
+ }
+}
diff --git a/examples/todomvc/static/favicon.png b/examples/todomvc/static/favicon.png
new file mode 100644
index 0000000..825b9e6
Binary files /dev/null and b/examples/todomvc/static/favicon.png differ
diff --git a/examples/todomvc/svelte.config.js b/examples/todomvc/svelte.config.js
new file mode 100644
index 0000000..87f198f
--- /dev/null
+++ b/examples/todomvc/svelte.config.js
@@ -0,0 +1,15 @@
+import adapter from '@sveltejs/adapter-auto';
+import { vitePreprocess } from '@sveltejs/kit/vite';
+
+/** @type {import('@sveltejs/kit').Config} */
+const config = {
+ // Consult https://kit.svelte.dev/docs/integrations#preprocessors
+ // for more information about preprocessors
+ preprocess: vitePreprocess(),
+
+ kit: {
+ adapter: adapter()
+ }
+};
+
+export default config;
diff --git a/examples/todomvc/tsconfig.json b/examples/todomvc/tsconfig.json
new file mode 100644
index 0000000..6ae0c8c
--- /dev/null
+++ b/examples/todomvc/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "./.svelte-kit/tsconfig.json",
+ "compilerOptions": {
+ "allowJs": true,
+ "checkJs": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "strict": true
+ }
+ // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
+ //
+ // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
+ // from the referenced tsconfig.json - TypeScript does not merge them in
+}
diff --git a/examples/todomvc/vite.config.js b/examples/todomvc/vite.config.js
new file mode 100644
index 0000000..8747050
--- /dev/null
+++ b/examples/todomvc/vite.config.js
@@ -0,0 +1,8 @@
+import { sveltekit } from '@sveltejs/kit/vite';
+
+/** @type {import('vite').UserConfig} */
+const config = {
+ plugins: [sveltekit()]
+};
+
+export default config;