├── .npmrc ├── static ├── favicon.png └── robots.txt ├── src ├── lib │ └── images │ │ ├── svelte-welcome.png │ │ ├── svelte-welcome.webp │ │ ├── github.svg │ │ └── svelte-logo.svg ├── routes │ ├── +page.ts │ ├── about │ │ ├── +page.ts │ │ └── +page.svelte │ ├── sverdle │ │ ├── how-to-play │ │ │ ├── +page.ts │ │ │ └── +page.svelte │ │ ├── reduced-motion.ts │ │ ├── +page.server.ts │ │ ├── game.ts │ │ └── +page.svelte │ ├── +layout.svelte │ ├── +page.svelte │ ├── styles.css │ ├── Counter.svelte │ └── Header.svelte ├── app.d.ts └── app.html ├── vite.config.ts ├── .gitignore ├── .eslintignore ├── .prettierignore ├── .prettierrc ├── .eslintrc.cjs ├── tsconfig.json ├── svelte.config.js ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thisdot/sveltekit-workshop/HEAD/static/favicon.png -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/lib/images/svelte-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thisdot/sveltekit-workshop/HEAD/src/lib/images/svelte-welcome.png -------------------------------------------------------------------------------- /src/lib/images/svelte-welcome.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thisdot/sveltekit-workshop/HEAD/src/lib/images/svelte-welcome.webp -------------------------------------------------------------------------------- /src/routes/+page.ts: -------------------------------------------------------------------------------- 1 | // since there's no dynamic data here, we can prerender 2 | // it so that it gets served as a static asset in production 3 | export const prerender = true; 4 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | node_modules 4 | /build 5 | /.svelte-kit 6 | /package 7 | .env 8 | .env.* 9 | !.env.example 10 | .vercel 11 | .output 12 | vite.config.js.timestamp-* 13 | vite.config.ts.timestamp-* 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface Platform {} 9 | } 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /src/routes/about/+page.ts: -------------------------------------------------------------------------------- 1 | import { dev } from '$app/environment'; 2 | 3 | // we don't need any JS on this page, though we'll load 4 | // it in dev so that we get hot module replacement 5 | export const csr = dev; 6 | 7 | // since there's no dynamic data here, we can prerender 8 | // it so that it gets served as a static asset in production 9 | export const prerender = true; 10 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/routes/sverdle/how-to-play/+page.ts: -------------------------------------------------------------------------------- 1 | import { dev } from '$app/environment'; 2 | 3 | // we don't need any JS on this page, though we'll load 4 | // it in dev so that we get hot module replacement 5 | export const csr = dev; 6 | 7 | // since there's no dynamic data here, we can prerender 8 | // it so that it gets served as a static asset in production 9 | export const prerender = true; 10 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/kit/vite'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter() 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /src/routes/sverdle/reduced-motion.ts: -------------------------------------------------------------------------------- 1 | import { readable } from 'svelte/store'; 2 | import { browser } from '$app/environment'; 3 | 4 | const reduced_motion_query = '(prefers-reduced-motion: reduce)'; 5 | 6 | const get_initial_motion_preference = () => { 7 | if (!browser) return false; 8 | return window.matchMedia(reduced_motion_query).matches; 9 | }; 10 | 11 | export const reduced_motion = readable(get_initial_motion_preference(), (set) => { 12 | if (browser) { 13 | const set_reduced_motion = (event: MediaQueryListEvent) => { 14 | set(event.matches); 15 | }; 16 | const media_query_list = window.matchMedia(reduced_motion_query); 17 | media_query_list.addEventListener('change', set_reduced_motion); 18 | 19 | return () => { 20 | media_query_list.removeEventListener('change', set_reduced_motion); 21 | }; 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /src/routes/about/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | About 3 | 4 | 5 | 6 |
7 |

About this app

8 | 9 |

10 | This is a SvelteKit app. You can make your own by typing the 11 | following into your command line and following the prompts: 12 |

13 | 14 |
npm create svelte@latest
15 | 16 |

17 | The page you're looking at is purely static HTML, with no client-side interactivity needed. 18 | Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening 19 | the devtools network panel and reloading. 20 |

21 | 22 |

23 | The Sverdle page illustrates SvelteKit's data loading and form handling. Try 24 | using it with JavaScript disabled! 25 |

26 |
27 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 | 9 |
10 | 11 |
12 | 13 | 16 |
17 | 18 | 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sveltekit-workshop", 3 | "version": "0.0.1", 4 | "private": "true", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite dev", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 12 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 13 | "format": "prettier --plugin-search-dir . --write ." 14 | }, 15 | "devDependencies": { 16 | "@fontsource/fira-mono": "^4.5.10", 17 | "@neoconfetti/svelte": "^1.0.0", 18 | "@sveltejs/adapter-auto": "^2.0.0", 19 | "@sveltejs/kit": "^1.5.0", 20 | "@types/cookie": "^0.5.1", 21 | "@typescript-eslint/eslint-plugin": "^5.45.0", 22 | "@typescript-eslint/parser": "^5.45.0", 23 | "eslint": "^8.28.0", 24 | "eslint-config-prettier": "^8.5.0", 25 | "eslint-plugin-svelte3": "^4.0.0", 26 | "prettier": "^2.8.0", 27 | "prettier-plugin-svelte": "^2.8.1", 28 | "svelte": "^3.54.0", 29 | "svelte-check": "^3.0.1", 30 | "tslib": "^2.4.1", 31 | "typescript": "^5.0.0", 32 | "vite": "^4.2.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | Home 9 | 10 | 11 | 12 |
13 |

14 | 15 | 16 | 17 | Welcome 18 | 19 | 20 | 21 | to your new
SvelteKit app 22 |

23 | 24 |

25 | try editing src/routes/+page.svelte 26 |

27 | 28 | 29 |
30 | 31 | 60 | -------------------------------------------------------------------------------- /src/lib/images/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 16 | -------------------------------------------------------------------------------- /src/lib/images/svelte-logo.svg: -------------------------------------------------------------------------------- 1 | svelte-logo -------------------------------------------------------------------------------- /src/routes/sverdle/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { fail } from '@sveltejs/kit'; 2 | import { Game } from './game'; 3 | import type { PageServerLoad, Actions } from './$types'; 4 | 5 | export const load = (({ cookies }) => { 6 | const game = new Game(cookies.get('sverdle')); 7 | 8 | return { 9 | /** 10 | * The player's guessed words so far 11 | */ 12 | guesses: game.guesses, 13 | 14 | /** 15 | * An array of strings like '__x_c' corresponding to the guesses, where 'x' means 16 | * an exact match, and 'c' means a close match (right letter, wrong place) 17 | */ 18 | answers: game.answers, 19 | 20 | /** 21 | * The correct answer, revealed if the game is over 22 | */ 23 | answer: game.answers.length >= 6 ? game.answer : null 24 | }; 25 | }) satisfies PageServerLoad; 26 | 27 | export const actions = { 28 | /** 29 | * Modify game state in reaction to a keypress. If client-side JavaScript 30 | * is available, this will happen in the browser instead of here 31 | */ 32 | update: async ({ request, cookies }) => { 33 | const game = new Game(cookies.get('sverdle')); 34 | 35 | const data = await request.formData(); 36 | const key = data.get('key'); 37 | 38 | const i = game.answers.length; 39 | 40 | if (key === 'backspace') { 41 | game.guesses[i] = game.guesses[i].slice(0, -1); 42 | } else { 43 | game.guesses[i] += key; 44 | } 45 | 46 | cookies.set('sverdle', game.toString()); 47 | }, 48 | 49 | /** 50 | * Modify game state in reaction to a guessed word. This logic always runs on 51 | * the server, so that people can't cheat by peeking at the JavaScript 52 | */ 53 | enter: async ({ request, cookies }) => { 54 | const game = new Game(cookies.get('sverdle')); 55 | 56 | const data = await request.formData(); 57 | const guess = data.getAll('guess') as string[]; 58 | 59 | if (!game.enter(guess)) { 60 | return fail(400, { badGuess: true }); 61 | } 62 | 63 | cookies.set('sverdle', game.toString()); 64 | }, 65 | 66 | restart: async ({ cookies }) => { 67 | cookies.delete('sverdle'); 68 | } 69 | } satisfies Actions; 70 | -------------------------------------------------------------------------------- /src/routes/sverdle/game.ts: -------------------------------------------------------------------------------- 1 | import { words, allowed } from './words.server'; 2 | 3 | export class Game { 4 | index: number; 5 | guesses: string[]; 6 | answers: string[]; 7 | answer: string; 8 | 9 | /** 10 | * Create a game object from the player's cookie, or initialise a new game 11 | */ 12 | constructor(serialized: string | undefined = undefined) { 13 | if (serialized) { 14 | const [index, guesses, answers] = serialized.split('-'); 15 | 16 | this.index = +index; 17 | this.guesses = guesses ? guesses.split(' ') : []; 18 | this.answers = answers ? answers.split(' ') : []; 19 | } else { 20 | this.index = Math.floor(Math.random() * words.length); 21 | this.guesses = ['', '', '', '', '', '']; 22 | this.answers = []; 23 | } 24 | 25 | this.answer = words[this.index]; 26 | } 27 | 28 | /** 29 | * Update game state based on a guess of a five-letter word. Returns 30 | * true if the guess was valid, false otherwise 31 | */ 32 | enter(letters: string[]) { 33 | const word = letters.join(''); 34 | const valid = allowed.has(word); 35 | 36 | if (!valid) return false; 37 | 38 | this.guesses[this.answers.length] = word; 39 | 40 | const available = Array.from(this.answer); 41 | const answer = Array(5).fill('_'); 42 | 43 | // first, find exact matches 44 | for (let i = 0; i < 5; i += 1) { 45 | if (letters[i] === available[i]) { 46 | answer[i] = 'x'; 47 | available[i] = ' '; 48 | } 49 | } 50 | 51 | // then find close matches (this has to happen 52 | // in a second step, otherwise an early close 53 | // match can prevent a later exact match) 54 | for (let i = 0; i < 5; i += 1) { 55 | if (answer[i] === '_') { 56 | const index = available.indexOf(letters[i]); 57 | if (index !== -1) { 58 | answer[i] = 'c'; 59 | available[index] = ' '; 60 | } 61 | } 62 | } 63 | 64 | this.answers.push(answer.join('')); 65 | 66 | return true; 67 | } 68 | 69 | /** 70 | * Serialize game state so it can be set as a cookie 71 | */ 72 | toString() { 73 | return `${this.index}-${this.guesses.join(' ')}-${this.answers.join(' ')}`; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/routes/styles.css: -------------------------------------------------------------------------------- 1 | @import '@fontsource/fira-mono'; 2 | 3 | :root { 4 | --font-body: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, 5 | Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 6 | --font-mono: 'Fira Mono', monospace; 7 | --color-bg-0: rgb(202, 216, 228); 8 | --color-bg-1: hsl(209, 36%, 86%); 9 | --color-bg-2: hsl(224, 44%, 95%); 10 | --color-theme-1: #ff3e00; 11 | --color-theme-2: #4075a6; 12 | --color-text: rgba(0, 0, 0, 0.7); 13 | --column-width: 42rem; 14 | --column-margin-top: 4rem; 15 | font-family: var(--font-body); 16 | color: var(--color-text); 17 | } 18 | 19 | body { 20 | min-height: 100vh; 21 | margin: 0; 22 | background-attachment: fixed; 23 | background-color: var(--color-bg-1); 24 | background-size: 100vw 100vh; 25 | background-image: radial-gradient( 26 | 50% 50% at 50% 50%, 27 | rgba(255, 255, 255, 0.75) 0%, 28 | rgba(255, 255, 255, 0) 100% 29 | ), 30 | linear-gradient(180deg, var(--color-bg-0) 0%, var(--color-bg-1) 15%, var(--color-bg-2) 50%); 31 | } 32 | 33 | h1, 34 | h2, 35 | p { 36 | font-weight: 400; 37 | } 38 | 39 | p { 40 | line-height: 1.5; 41 | } 42 | 43 | a { 44 | color: var(--color-theme-1); 45 | text-decoration: none; 46 | } 47 | 48 | a:hover { 49 | text-decoration: underline; 50 | } 51 | 52 | h1 { 53 | font-size: 2rem; 54 | text-align: center; 55 | } 56 | 57 | h2 { 58 | font-size: 1rem; 59 | } 60 | 61 | pre { 62 | font-size: 16px; 63 | font-family: var(--font-mono); 64 | background-color: rgba(255, 255, 255, 0.45); 65 | border-radius: 3px; 66 | box-shadow: 2px 2px 6px rgb(255 255 255 / 25%); 67 | padding: 0.5em; 68 | overflow-x: auto; 69 | color: var(--color-text); 70 | } 71 | 72 | .text-column { 73 | display: flex; 74 | max-width: 48rem; 75 | flex: 0.6; 76 | flex-direction: column; 77 | justify-content: center; 78 | margin: 0 auto; 79 | } 80 | 81 | input, 82 | button { 83 | font-size: inherit; 84 | font-family: inherit; 85 | } 86 | 87 | button:focus:not(:focus-visible) { 88 | outline: none; 89 | } 90 | 91 | @media (min-width: 720px) { 92 | h1 { 93 | font-size: 2.4rem; 94 | } 95 | } 96 | 97 | .visually-hidden { 98 | border: 0; 99 | clip: rect(0 0 0 0); 100 | height: auto; 101 | margin: 0; 102 | overflow: hidden; 103 | padding: 0; 104 | position: absolute; 105 | width: 1px; 106 | white-space: nowrap; 107 | } 108 | -------------------------------------------------------------------------------- /src/routes/Counter.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 | 22 | 23 |
24 |
25 | 26 | {Math.floor($displayed_count)} 27 |
28 |
29 | 30 | 35 |
36 | 37 | 103 | -------------------------------------------------------------------------------- /src/routes/sverdle/how-to-play/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | How to play Sverdle 3 | 4 | 5 | 6 |
7 |

How to play Sverdle

8 | 9 |

10 | Sverdle is a clone of Wordle, the 11 | word guessing game. To play, enter a five-letter English word. For example: 12 |

13 | 14 |
15 | r 16 | i 17 | t 18 | z 19 | y 20 |
21 | 22 |

23 | The y is in the right place. r and 24 | t 25 | are the right letters, but in the wrong place. The other letters are wrong, and can be discarded. 26 | Let's make another guess: 27 |

28 | 29 |
30 | p 31 | a 32 | r 33 | t 34 | y 35 |
36 | 37 |

This time we guessed right! You have six guesses to get the word.

38 | 39 |

40 | Unlike the original Wordle, Sverdle runs on the server instead of in the browser, making it 41 | impossible to cheat. It uses <form> and cookies to submit data, meaning you can 42 | even play with JavaScript disabled! 43 |

44 |
45 | 46 | 96 | -------------------------------------------------------------------------------- /src/routes/Header.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 | SvelteKit 11 | 12 |
13 | 14 | 33 | 34 |
35 | 36 | GitHub 37 | 38 |
39 |
40 | 41 | 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Delightful Web Development with SvelteKit 2 | 3 | ### SvelteKit Workshop by [This Dot Labs](https://thisdot.co) 4 | 5 | Clone this repository to get started with the workshop. 6 | 7 | ## Recommended IDE Setup 8 | 9 | [VS Code](https://code.visualstudio.com/) + [Svelte Extension Pack](https://marketplace.visualstudio.com/items?itemName=1YiB.svelte-bundle). 10 | 11 | ## Module Branches 12 | 13 | There are different branches for each module and sub-module in the workshop. You can checkout the branch for the module you are working on by running the following command: 14 | 15 | ```bash 16 | git checkout module-1 17 | 18 | git checkout module-2.2 19 | ``` 20 | 21 | If you get stuck, you can checkout the branch with the appended `-solution` to the name. Solution branches are provided for each module and submodule. 22 | 23 | Branches: 24 | `main` 25 | `module-1` 26 | `module-2.1` 27 | `module-2.1-solution` 28 | `module-2.2` 29 | `module-2.2-solution` 30 | `module-2.3` 31 | `module-2.3-solution` 32 | `module-2.4` 33 | `module-3.1` 34 | `module-3.2` 35 | `module-3.3` 36 | `module-3.4` 37 | `module-3.4-solution` 38 | `module-3.5` 39 | `module-4.1` 40 | `module-4.1-solution` 41 | `module-4.2` 42 | `module-4.2-solution` 43 | `module-4.3` 44 | `module-4.4` 45 | `module-4.4-solution` 46 | `module-4.5` 47 | `module-4.5-solution` 48 | `module-4.6` 49 | `module-5.0` 50 | `module-5.0-solution` 51 | `module-6.1` 52 | `api` (for local use API server) 53 | 54 | ## Workshop Outline 55 | 56 | ### Module 1: Svelte and SvelteKit 57 | 58 | Learn about the relationship between Svelte and SvelteKit, what SvelteKit brings to the table. 59 | 60 | In this module we will cover: 61 | 62 | - Svelte and SvelteKit 63 | - Setting up a new SvelteKit application for development 64 | 65 | ### Module 2: Routing 66 | 67 | SvelteKit provides robust filesystem-based routing for your application. 68 | 69 | In this module we’ll learn about: 70 | 71 | - SvelteKit’s directory driven filesystem-based router 72 | - Loading data for SvelteKit pages 73 | - SvelteKit’s Layouts and Error Pages 74 | 75 | ### Module 3: Even more Routing 76 | 77 | To be as flexible as possible, SvelteKit provides many routing features so you can create the URL structure you need 78 | 79 | - Rest params, optional params and breaking out of layouts 80 | - Routing Matchers 81 | - Route Priority 82 | 83 | ### Module 4: Server Side Svelte 84 | 85 | SvelteKit allows you to restrict and isolate code to only ever be run on the server, never exposing it to the client. With that understanding we will learn about: 86 | 87 | - Using Secrets and Environment Variables 88 | - Creating API endpoints 89 | - SvelteKit Hooks 90 | 91 | ### Module 5: Forms 92 | 93 | SvelteKit’s approach to forms and data mutation is simple: use the platform, then progressively enhance the experience. To facilitate that we will explore: 94 | 95 | - Handling for submissions with SvelteKit Actions 96 | - Enhancing forms with JavaScript on the client 97 | 98 | ### Module 6: Deployment 99 | 100 | SvelteKit offers many options for how to build your site for production all the way down to the page level. In this module we’ll discuss: 101 | 102 | - SvelteKits Page Options 103 | - Adapters 104 | 105 | ### [Course Prerequisites] 106 | 107 | - Computer with internet access 108 | - A microphone and a webcam 109 | - An IDE of choice (we will be using Visual Studio Code) 110 | - Version 16+ or newer of Node installed on your computer 111 | - Basic understanding of HTML and CSS 112 | - Good understanding of JavaScript / TypeScript (all the examples will be in Typescript) 113 | - Previous Knowledge of Svelte - the component framework SvelteKit is built on top of - is recommended 114 | -------------------------------------------------------------------------------- /src/routes/sverdle/+page.svelte: -------------------------------------------------------------------------------- 1 | 83 | 84 | 85 | 86 | 87 | Sverdle 88 | 89 | 90 | 91 |

Sverdle

92 | 93 |
{ 97 | // prevent default callback from resetting the form 98 | return ({ update }) => { 99 | update({ reset: false }); 100 | }; 101 | }} 102 | > 103 | How to play 104 | 105 |
106 | {#each Array(6) as _, row} 107 | {@const current = row === i} 108 |

Row {row + 1}

109 |
110 | {#each Array(5) as _, column} 111 | {@const answer = data.answers[row]?.[column]} 112 | {@const value = data.guesses[row]?.[column] ?? ''} 113 | {@const selected = current && column === data.guesses[row].length} 114 | {@const exact = answer === 'x'} 115 | {@const close = answer === 'c'} 116 | {@const missing = answer === '_'} 117 |
118 | {value} 119 | 120 | {#if exact} 121 | (correct) 122 | {:else if close} 123 | (present) 124 | {:else if missing} 125 | (absent) 126 | {:else} 127 | empty 128 | {/if} 129 | 130 | 131 |
132 | {/each} 133 |
134 | {/each} 135 |
136 | 137 |
138 | {#if won || data.answers.length >= 6} 139 | {#if !won && data.answer} 140 |

the answer was "{data.answer}"

141 | {/if} 142 | 145 | {:else} 146 |
147 | 148 | 149 | 158 | 159 | {#each ['qwertyuiop', 'asdfghjkl', 'zxcvbnm'] as row} 160 |
161 | {#each row as letter} 162 | 174 | {/each} 175 |
176 | {/each} 177 |
178 | {/if} 179 |
180 |
181 | 182 | {#if won} 183 |
193 | {/if} 194 | 195 | 407 | --------------------------------------------------------------------------------