Migrating to 0.16.0

Lots changed in 0.16.0. This document outlines the steps you will have to take to upgrade the houdini-related portions of your app. We recommend doing this while you upgrade your SvelteKit version since you will be checking/modifying the same files.

npx svelte-migrate routes

1. Configuration files

./houdini.config.js

client and include are the only two things that you need to add here.

  • client needs to be set to a relative path from houdini.config.js to a file in your project that default exports your client. It can point to a typescript file if appropriate.
  • include replaces sourceGlob but it’s not required anymore. The default value is src/**/*.{svelte,graphql,gql,ts,js} so unless you are doing something special, you can probably just delete it. If you are doing something special, make sure that you include .js in your extensions so that the generated +page.js can be picked up if you use an automatic loader. Keep in mind there is a new exclude value that might be better suited to your situation.
  • apiUrl has a slightly new behavior. It now controls whether or not the vite plugin will poll for schema changes. If the latest version of your schema is available locally then you should just omit the value. This is common in monorepos and GraphQL apis that are resolved with a SvelteKit endpoint.

  export default {
+   client: "./src/lib/client.ts",
    apiUrl: "http://localhost:4000/graphql",
-   sourceGlob: "./src/**/*.{svelte,graphql,gql},
-   schemaPath: './schema.graphql',
-   framework: "kit",
  }

There are also new defaults that likely cover your situation. If you are interested in cleaning up your config some more, click on this Deep Dive:

./vite.config.js

  import sveltekit from '@sveltejs/kit/vite'
+ import houdini from 'houdini/vite'

  export default {
+   plugins: [houdini(), sveltekit()],

-   server: {
-     fs: {
-         allow: ['.']
-     }
-   }
  }

Update your vite config file to pass Houdini’s new plugin before the sveltekit one. With this set up, you’ll never have to run generate again! Also, while you’re here, you can delete that ugly server.fs.allow config you had before.

./svelte.config.js

  import adapter from '@sveltejs/adapter-auto'
- import houdini from 'houdini/preprocess'
  import path from 'path'
  import preprocess from 'svelte-preprocess'

  /** @type {import('@sveltejs/kit').Config} */
  const config = {
-  	preprocess: [preprocess(), houdini()],
+  	preprocess: [preprocess()],

  	kit: {
  		adapter: adapter(),

  		alias: {
  			$houdini: path.resolve('./$houdini'),
  			$lib: path.resolve('./src/lib'),
  		},
  	},
  }

  export default config

Remove the old preprocessor but leave the alias config.

src/routes/+layout.svelte

- import client from '../houdiniClient'
-
- client.init()

You no longer need to call client.init

2. Inline Documents

Apart from fragment and paginatedFragment, all functions that used to wrap inline documents have been removed. You will now just use the result of the graphql tag directly (as shown in the query example below).

2.a Inline Queries

A majority of the work will be done by the SvelteKit migration tool. Anything that used to be defined in context="module" needs to be in +page.js (or the typescript equivalent). This includes variable functions, hooks, etc. You can use the generated @migration comments to track which queries need updating.

Apart from the actual query function being removed, the way you access your query data has changed.

  <script>
-   const { data } = query(graphql`
+   const UserInfo = graphql`
      query UserInfo {
        viewer {
          firstName
        }
      }
-    `)
+    `
  </script>

  <div>
-    {$data.viewer.firstName}
+    {$UserInfo.data.viewer.firstName}
  </div>

You can no longer destructure data when using query. Instead, query returns a store that holds an object with the data. Methods like fetch and loadNextPage are attached to the store itself and do not need the $:

- <button on:click={loadNextPage}>
+ <button on:click={MyStore.loadNextPage}>
    load next
  </button>

afterLoad hooks also now take the whole load event as a single event parameter:

- export async function afterLoad({ params, data }) {
+ export async function afterLoad({ event, data }) {
    return {
-     message: params.message
+     message: event.params.message
    }
  }

2.b Inline Fragments

The order for the arguments to inline fragments has been inverted.

  <script>
-   const avatarInfo = fragment(graphql`
+   const avatarInfo = fragment(user, graphql`
        viewer {
          firstName
        }
      }
-     `,
-     user
-   )
+   `)
  </script>

  <div>
     {$avatarInfo.firstName}
  </div>

2.c Inline Pagination

Paginated handlers now get their arguments as an object:

  <script>
    const userList = graphql`
        friends @paginate {
          firstName
        }
    `
  </script>

- <button on:click={() => userList.loadNextPage(10)}>
+ <button on:click={() => userList.loadNextPage({next: 10})}>
    load next
  </button>

3. External Queries

There are 2 different parts to change for every route that used an external document: +page.svelte and +page.js

3.a Page components (+page.svelte)

Instead of importing the stores, your components now get them from a prop passed to your routes. Typescript users can use the standard PageData provided by SvelteKit to get type information for the store. This not only simplifies the mental model but also means we can remove a lot of boilerplate:

  <script>
-   import { browser } from ' $app/environment'
-   import { GQL_MyUserInfo } from '$houdini'
+   export let data

+   $: ({ MyUserInfo } = data)
-   $: browser && GQL_MyUserInfo.fetch()
  </script>

+ {$MyUserInfo.data.viewer.id}
- {$GQL_MyUserInfo.data.viewer.id}

3.b Page loaders (+page.js or +page.ts)

The second thing you need to update is the load function. The official migration script will have already moved everything into the +page.js/ts files but you now need to modify the load function to return a prop that points to your store. To streamline this, Houdini provides a new family of utility functions:

- import { GQL_UserInfo } from "$houdini"
+ import { load_UserInfo } from "$houdini"

  export async function load(event) {
-   GQL_UserInfo.fetch({event, variables: { ... }})
-   return {}
+   return {
+     ...await load_MyUserInfo({event, variables: { ... }})
+   }
  }

By the way, what we used to call “external documents” we now refer to as “manual loading” since Houdini can now generate automatic loaders for your external files. Interested? Check out the new Working with GraphQL guide.

4. External Mutations

The mutate method of mutation stores now reflects the inline equivalent. The first argument is the variables for the mutation and any additional config comes after:

  <script>
    import { GQL_MyUpdate } from '$houdini'
  </script>

- <button on:click={() => GQL_MyUpdate.mutate({ variables: { ... } policy: 'NetworkOnly' })}>
+ <button on:click={() => GQL_MyUpdate.mutate({ ... }, { policy: 'NetworkOnly' })}>
    submit
  </button>

5. TypeScript Config

Houdini now provides type definitions for all of your load functions (variables, hooks, etc). To set this up, you have to add $houdini/types as a rootDir in your typescript config file:

{
	// ...
	"compilerOptions": {
		// ...
		"rootDirs": [".", "./.svelte-kit/types", "./$houdini/types"]
	}
}

That’s it!

Good luck with your migration! We appreciate your patience as Houdini adapts to SvelteKit’s changes. Hopefully you agree that this new world is a major improvement from where we were a month ago. At the very least, the variables API for automatic loaders doesn’t feel so awkward any more 😅