mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Sveltekit tutorial (#6538)
Simple Sveltekit tutorial --------- Co-authored-by: Nnenna Ndukwe <nnenna.s.ndukwe@gmail.com>
This commit is contained in:
		
							parent
							
								
									3fc8a4f9f4
								
							
						
					
					
						commit
						3539f3db02
					
				
							
								
								
									
										
											BIN
										
									
								
								website/docs/feature-flag-tutorials/sveltekit/20240202174256.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								website/docs/feature-flag-tutorials/sveltekit/20240202174256.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 61 KiB | 
							
								
								
									
										
											BIN
										
									
								
								website/docs/feature-flag-tutorials/sveltekit/feat.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								website/docs/feature-flag-tutorials/sveltekit/feat.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 408 KiB | 
| @ -0,0 +1,273 @@ | |||||||
|  | --- | ||||||
|  | title: How to Implement Feature Flags in SvelteKit | ||||||
|  | description: "How to use Unleash feature flags with SvelteKit." | ||||||
|  | --- | ||||||
|  | 
 | ||||||
|  | Hello and welcome to another tutorial. This is about adding feature flags to an app made with [SvelteKit](https://kit.svelte.dev/), [Unleash](https://www.getunleash.io/) and the official [Unleash Svelte SDK](https://docs.getunleash.io/reference/sdks/svelte). | ||||||
|  | 
 | ||||||
|  | We'll make a paired-down habits app to keep track of your new year's resolutions. The feature flag will be used to change the number of habits a user can add. | ||||||
|  | 
 | ||||||
|  | While this is not meant to be a complete product, we can leverage feature flags using a full stack framework like Next.js or SvelteKit. The completed code for this implementation is available in [a Github repository](https://github.com/alvinometric/unleash-sveltekit). | ||||||
|  | 
 | ||||||
|  | - [Setup](#setup) | ||||||
|  | - [Create a basic habits app](#create-a-basic-habits-app) | ||||||
|  | - [Adding habits and premium features](#adding-habits-and-premium-features) | ||||||
|  | - [Showing a different component based on the feature flag](#showing-a-different-component-based-on-the-feature-flag) | ||||||
|  | - [Conclusion](#conclusion) | ||||||
|  | 
 | ||||||
|  | ## Setup | ||||||
|  | 
 | ||||||
|  | Create a skeleton SvelteKit project named "habits". | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | npm create svelte@latest habits | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | We'll need a few more dependencies. You can install these in one command below: | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | npm i date-fns @unleash/proxy-client-svelte | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Create a basic habits app | ||||||
|  | 
 | ||||||
|  | We'll use Svelte stores to keep track of a global array of habits. For the sake of simplicity, we won't store these habits anywhere yet (feel free to add localStorage or a database). Our basic habit app will only consist of 3 files. | ||||||
|  | 
 | ||||||
|  | First, a global store that will contain our habits and their completion dates. Just JavaScript, no Svelte yet. | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | // src/lib/stores.js | ||||||
|  | import { writable } from "svelte/store"; | ||||||
|  | 
 | ||||||
|  | export const habitStore = writable([ | ||||||
|  |   { | ||||||
|  |     id: 1, | ||||||
|  |     name: "Walk 10k steps", | ||||||
|  |     completedDays: [], | ||||||
|  |   }, | ||||||
|  | ]); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Then, we'll create an `App.svelte` file for our main logic. | ||||||
|  | 
 | ||||||
|  | ```svelte | ||||||
|  | <script> | ||||||
|  |   // src/lib/App.svelte | ||||||
|  |   import { format, addDays } from 'date-fns'; | ||||||
|  |   import Habit from '$lib/Habit.svelte'; | ||||||
|  |   import { habitStore } from '$lib/stores.js'; | ||||||
|  |   import AddHabit from '../lib/AddHabit.svelte'; | ||||||
|  |   let maxHabits = 2; | ||||||
|  | 
 | ||||||
|  |   // go back 5 days | ||||||
|  |   const dates = new Array(5).fill(0).map((_, i) => { | ||||||
|  |     let today = new Date(); | ||||||
|  |     return addDays(today, -i); | ||||||
|  |   }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <AddHabit {maxHabits} /> | ||||||
|  | 
 | ||||||
|  | <table> | ||||||
|  |   <thead> | ||||||
|  |     <tr> | ||||||
|  |       <th>Habit</th> | ||||||
|  |       {#each dates as date} | ||||||
|  |         <th>{format(date, 'MMM do')}</th> | ||||||
|  |       {/each} | ||||||
|  |     </tr> | ||||||
|  |   </thead> | ||||||
|  | 
 | ||||||
|  |   <tbody> | ||||||
|  |     {#each $habitStore as habit} | ||||||
|  |       <Habit {habit} {dates} /> | ||||||
|  |     {/each} | ||||||
|  |   </tbody> | ||||||
|  | </table> | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Next, update the `+page.svelte` file (our index route) to include our app. | ||||||
|  | 
 | ||||||
|  | ```svelte | ||||||
|  | <script> | ||||||
|  |   // src/routes/+page.svelte | ||||||
|  |   import App from '../lib/App.svelte'; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <App /> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | To complete the basic setup of the app, add a component for each habit that be checked on and off using this code snippet: | ||||||
|  | 
 | ||||||
|  | ```svelte | ||||||
|  | <script> | ||||||
|  |   // src/lib/Habit.svelte | ||||||
|  |   import { habitStore } from '$lib/stores.js'; | ||||||
|  |   import { format } from 'date-fns'; | ||||||
|  | 
 | ||||||
|  |   export let habit; | ||||||
|  |   export let dates; | ||||||
|  | 
 | ||||||
|  |   function toggleDay(day) { | ||||||
|  |     let updatedDays = [...habit.completedDays]; | ||||||
|  | 
 | ||||||
|  |     const index = updatedDays.indexOf(day); | ||||||
|  |     if (index !== -1) { | ||||||
|  |       updatedDays.splice(index, 1); | ||||||
|  |     } else { | ||||||
|  |       updatedDays.push(day); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     habitStore.update((items) => { | ||||||
|  |       return items.map((item) => { | ||||||
|  |         if (item.id === habit.id) { | ||||||
|  |           return { ...item, completedDays: updatedDays }; | ||||||
|  |         } | ||||||
|  |         return item; | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <tr> | ||||||
|  |   <td>{habit.name}</td> | ||||||
|  | 
 | ||||||
|  |   {#each dates as date} | ||||||
|  |     <td> | ||||||
|  |       <input | ||||||
|  |         type="checkbox" | ||||||
|  |         on:click={() => toggleDay(date)} | ||||||
|  |         checked={habit.completedDays.includes(date)} | ||||||
|  |       /> | ||||||
|  |       {format(date, 'MMM do')} | ||||||
|  |     </td> | ||||||
|  |   {/each} | ||||||
|  | </tr> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Now we have a fully functioning Svelte app in all its glory! Essentially, it's a table with checkboxes. | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | ## Adding habits and premium features | ||||||
|  | 
 | ||||||
|  | We have the basics of the app set up, but we could make it more user-friendly. Let's add some more functionality: | ||||||
|  | 
 | ||||||
|  | - Add the ability for users create their own habits | ||||||
|  | - Limit the number of habits a user can create to a certain amount so we can turn this into a commercial product. | ||||||
|  | 
 | ||||||
|  | Let's do all of this in another component named `AddHabit.svelte`. | ||||||
|  | 
 | ||||||
|  | ```svelte | ||||||
|  | <script> | ||||||
|  |   // src/lib/AddHabit.svelte | ||||||
|  |   import { habitStore } from '$lib/stores.js'; | ||||||
|  | 
 | ||||||
|  |   export let maxHabits = 3; | ||||||
|  | 
 | ||||||
|  |   let habitsFull = false; | ||||||
|  | 
 | ||||||
|  |   function addHabit(e) { | ||||||
|  |     let numHabits = $habitStore.length; | ||||||
|  | 
 | ||||||
|  |     if (numHabits === maxHabits) { | ||||||
|  |       habitsFull = true; | ||||||
|  |     } else { | ||||||
|  |       let form = e.target; | ||||||
|  |       const formData = new FormData(e.target); | ||||||
|  | 
 | ||||||
|  |       habitStore.update((items) => { | ||||||
|  |         items.push({ id: items.length + 1, name: formData.get('name'), completedDays: [] }); | ||||||
|  |         return items; | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       // reset the form | ||||||
|  |       form.reset(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <dialog open={habitsFull}> | ||||||
|  |   <h2>❌ Maximum Habits Reached</h2> | ||||||
|  |   <p>You can only have up to {maxHabits} on the free tier. Purchase a premium version to unlock more.</p> | ||||||
|  |   <form method="dialog"> | ||||||
|  |     <button>OK</button> | ||||||
|  |   </form> | ||||||
|  | </dialog> | ||||||
|  | 
 | ||||||
|  | <form on:submit|preventDefault={addHabit}> | ||||||
|  |   <input type="text" name="name" /> | ||||||
|  |   <button type="submit"> Add new habit </button> | ||||||
|  | </form> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | What's happening here? A few things: | ||||||
|  | 
 | ||||||
|  | - An input and a button to add new habits to the store, until an arbitrary limit is reached | ||||||
|  | - A `maxHabits` prop is used to determine that limit | ||||||
|  | - When this maximum limit is reached, a modal dialog opens | ||||||
|  | - We reset the form after submission to clear the input | ||||||
|  | 
 | ||||||
|  | <video | ||||||
|  |   width="600px" | ||||||
|  |   preload="metadata" | ||||||
|  |   autoplay | ||||||
|  |   loop | ||||||
|  |   title="A maximum number of habits on the free tier" | ||||||
|  |   src="/media/habits.mp4" | ||||||
|  |   controls> </video> | ||||||
|  | 
 | ||||||
|  | ## Showing a different component based on the feature flag | ||||||
|  | 
 | ||||||
|  | On to the main topic, adding feature flags. | ||||||
|  | 
 | ||||||
|  | Go to your Unleash dashboard, and create new project (you're welcome to use the default project here). | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Next, create a feature flag called `maxHabitsIncreased`. | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Based on whether this flag is enabled or not, we'll set the `maxHabits` value to either 6 or 2. You could set this directly in a flag value if you wanted as well. | ||||||
|  | 
 | ||||||
|  | ### Basic toggle | ||||||
|  | 
 | ||||||
|  | We'll use the Svelte SDK to wrap a context provider around `App.svelte` like so: | ||||||
|  | 
 | ||||||
|  | ```svelte | ||||||
|  | <script> | ||||||
|  |   // src/routes/+page.svelte | ||||||
|  |   import App from '../lib/App.svelte'; | ||||||
|  |   import { FlagProvider } from '@unleash/proxy-client-svelte'; | ||||||
|  | 
 | ||||||
|  |   const config = { | ||||||
|  |     url: 'https://eu.app.unleash-hosted.com/jdfkdjfkd/api/frontend', // Your Front-end API | ||||||
|  |     clientKey: '', // Front-end API token (or proxy client key) | ||||||
|  |     appName: 'habits' | ||||||
|  |   }; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <FlagProvider {config}> | ||||||
|  |   <App /> | ||||||
|  | </FlagProvider> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Note that I’m using the URL and API key directly in the code right now, but you’d want to put these in an env file. | ||||||
|  | 
 | ||||||
|  | Now that our SDK is setup, we can modify our `App.svelte` to set the value of the variable based on the feature flag. | ||||||
|  | 
 | ||||||
|  | ```diff | ||||||
|  | +  import { useFlag } from '@unleash/proxy-client-svelte'; | ||||||
|  | +  const maxHabitsIncreased = useFlag('maxHabitsIncreased'); | ||||||
|  | +  let maxHabits = $maxHabitsIncreased ? 6 : 2; | ||||||
|  | -  lex maxHabits = 3; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Conclusion | ||||||
|  | 
 | ||||||
|  | You now have a SvelteKit app with feature flags. More precisely, you've learned: | ||||||
|  | 
 | ||||||
|  | - How to make a habit tracking app with SvelteKit | ||||||
|  | - How to add a feature flag to a full stack app using Unleash | ||||||
|  | - The different approaches to feature flagging on a static vs SSR context | ||||||
							
								
								
									
										
											BIN
										
									
								
								website/docs/feature-flag-tutorials/sveltekit/proj.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								website/docs/feature-flag-tutorials/sveltekit/proj.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 85 KiB | 
| @ -151,6 +151,11 @@ module.exports = { | |||||||
|                     label: 'Next.js', |                     label: 'Next.js', | ||||||
|                     id: 'feature-flag-tutorials/nextjs/implementing-feature-flags', |                     id: 'feature-flag-tutorials/nextjs/implementing-feature-flags', | ||||||
|                 }, |                 }, | ||||||
|  |                 { | ||||||
|  |                     type: 'doc', | ||||||
|  |                     label: 'Sveltekit', | ||||||
|  |                     id: 'feature-flag-tutorials/sveltekit/feature-flags-sveltekit', | ||||||
|  |                 }, | ||||||
|             ], |             ], | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|  | |||||||
| @ -9445,6 +9445,11 @@ prism-react-renderer@^1.3.1, prism-react-renderer@^1.3.5: | |||||||
|   resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz#786bb69aa6f73c32ba1ee813fbe17a0115435085" |   resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz#786bb69aa6f73c32ba1ee813fbe17a0115435085" | ||||||
|   integrity sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg== |   integrity sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg== | ||||||
| 
 | 
 | ||||||
|  | prism-svelte@^0.5.0: | ||||||
|  |   version "0.5.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/prism-svelte/-/prism-svelte-0.5.0.tgz#c4aeffeaddb179cfef213aab91ee785b66d22992" | ||||||
|  |   integrity sha512-db91Bf3pRGKDPz1lAqLFSJXeW13mulUJxhycysFpfXV5MIK7RgWWK2E5aPAa71s8TCzQUXxF5JOV42/iOs6QkA== | ||||||
|  | 
 | ||||||
| prismjs@^1.28.0: | prismjs@^1.28.0: | ||||||
|   version "1.29.0" |   version "1.29.0" | ||||||
|   resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" |   resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user