├── .github └── images │ └── svelte+php.gif ├── .gitignore ├── .nvmrc ├── .prettierrc ├── README.md ├── example ├── .gitignore ├── .npmrc ├── README.md ├── composer.json ├── package.json ├── src │ ├── app.d.ts │ ├── app.html │ ├── lib │ │ └── images │ │ │ ├── github.svg │ │ │ ├── svelte-logo.svg │ │ │ ├── svelte-welcome.png │ │ │ └── svelte-welcome.webp │ ├── php │ │ ├── Game.php │ │ └── Words.php │ └── routes │ │ ├── +layout.svelte │ │ ├── +page.svelte │ │ ├── +page.ts │ │ ├── Counter.svelte │ │ ├── Header.svelte │ │ ├── cowsay │ │ ├── +page.server.php │ │ └── +page.svelte │ │ ├── pherdle │ │ ├── +page.server.php │ │ ├── +page.svelte │ │ ├── how-to-play │ │ │ ├── +page.js │ │ │ └── +page.svelte │ │ └── reduced-motion.js │ │ ├── php │ │ ├── +page.svelte │ │ ├── [fruite] │ │ │ ├── +page.js │ │ │ ├── +page.server.php │ │ │ └── +page.svelte │ │ └── emoji │ │ │ └── [fruite] │ │ │ └── +server.php │ │ └── styles.css ├── static │ ├── favicon.png │ └── robots.txt ├── svelte.config.js ├── tsconfig.json └── vite.config.ts ├── package.json ├── playground └── index.php ├── src ├── index.ts └── runtime.ts ├── tsconfig.json └── yarn.lock /.github/images/svelte+php.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/basuke/vite-plugin-sveltekit-php-backend/3a23a45ccb4e26a8d6e98809d1221b7983c3a379/.github/images/svelte+php.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "semi": true, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Backend for SvelteKit 2 | 3 |

4 | Svelte + PHP 5 |

6 | 7 | You can have PHP logic directly in SvelteKit route directory like this: `+page.server.php` 8 | 9 | ```PHP 10 | params['name'] ?? 'unknownn'); 13 | return [ 14 | 'message' => "Hello " . $name, 15 | ]; 16 | } 17 | ``` 18 | 19 | You can also use PHP to write `form actions` or `endpoint`. 20 | 21 | Because SvelteKit talks Web standards, communication between SvelteKit and backend PHP server is pretty straight forward using HTTP semantics. 22 | 23 | - This plugin allows you to use SvelteKit the frontend of PHP backend. 24 | - Not like the one which uses SvelteKit as an assets manager. 25 | - No need to have a router in PHP side. SvelteKit is the router. 26 | - No need to have Nginx as a front end of PHP-fpm. Plugin communicate directly with PHP-fpm. 27 | - Of course, it's Composer compatible so that you can use any composer PHP packages including your own application logic. 28 | - It is obvious but you cannot create `+page.php` because there's no PHP runtime in client environment. 29 | 30 | ## How to run demo 31 | 32 | ### Prerequirements: 33 | 34 | - PHP with PHP fpm running 35 | - composer for PHP packages 36 | - Node.js 37 | 38 | ### Steps 39 | 40 | 1. cd to `example/` 41 | 2. `yarn` to install required packages. 42 | 3. `yarn dev --open` to launch dev server. 43 | 44 | Try pages and take a look into the code in `src/routes` 45 | 46 | ### src/routes/php/[fruite] 47 | 48 | > Example of page server load. The route parameters are passed to `+page.server.php`. Also it returns `$_SERVER` php variables for reference. 49 | 50 | ### src/routes/php/emoji/[fruite] 51 | 52 | > Example of endpoing. 53 | 54 | ### src/routes/pherdle 55 | 56 | > Example of form actions. Ported Sverdle to PHP. 57 | 58 | ### src/php 59 | 60 | > PHP library bound to `App\` namespace. You can place application logic in this folder. See `composer.json` for the configuration. 61 | 62 | ## Motivation of This Project 63 | 64 | You might ask, "Who uses PHP in 2023?" 65 | 66 | Well, I do. And many projects in the world are using PHP by various reasons. I want to use modern client technology and I can use Svelte component in our app easiliy. 67 | 68 | But not for SvelteKit. It's a complete web application framework and that's overlaps with the area PHP apps covers. As long as using PHP's routing feature, we cannot integrate SvelteKit's full power, such as unclear border between client and server, or prerendering during the build time. 69 | 70 | With this plugin, you can connect your business logics written in PHP directly to SvelteKit's page logic. You can replace following code from PHP to SvelteKit: 71 | 72 | - router 73 | - controller 74 | - page rendering 75 | 76 | ## No nginx required any more 77 | 78 | Usually your PHP application is served via PHP-fpm. Because PHP-fpm doesn't talk HTTP by itself, you usually place nginx in front of PHP-fpm. This plugin uses `fastcgi-kit` npm package (which is written by me for this project :p ) to communicate directly with PHP-fpm. 79 | 80 | On development phase, there's no need to launch PHP dev server any more. Just launch PHP-fpm service and any script will run from SvelteKit route. (Well, of course with under the restriction of PHP-fpm security). 81 | 82 | On production, you can replace `nginx` with node application (might be Express.js) to run SvelteKit application and every PHP call will be handled via FastCGI directly from the SvelteKit's page logic. 83 | 84 | > Building will be supported in the next version. 85 | 86 | ## Frontend is easier to replace than backend 87 | 88 | Suppose your codebase has been running for decades. Your app uses PHP for a historical reason. You are very passionate to make the application modernized. What can you do? 89 | 90 | Simple application is easy. You can rewrite the app from the scratch. But decent size of application includes many business logics in the code. 91 | 92 | Frontend trends such as user interface changes quickly but your businness logic isn't. At lease those changes should be unrelated to the frontend trends. 93 | 94 | ## License 95 | 96 | MIT 97 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | .vercel 10 | .output 11 | vite.config.js.timestamp-* 12 | vite.config.ts.timestamp-* 13 | vendor/ 14 | yarn-error.log 15 | composer.lock 16 | yarn.lock 17 | -------------------------------------------------------------------------------- /example/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ## How to run demo 2 | 3 | ### Prerequirements: 4 | 5 | - PHP with PHP fpm running 6 | - composer for PHP packages 7 | - Node.js 8 | 9 | ### Steps 10 | 11 | 1. cd to `example/` 12 | 2. `yarn` to install required packages. 13 | 3. `yarn dev --open` to launch dev server. 14 | 15 | Try pages and take a look into the code in `src/routes` 16 | 17 | ### routes/php/[fruite] 18 | 19 | > Example of page server load. The route parameters are passed to `+page.server.php`. Also it returns `$_SERVER` php variables for reference. 20 | 21 | ### routes/php/emoji/[fruite] 22 | 23 | > Example of endpoing. 24 | 25 | ### routes/pherdle 26 | 27 | > Example of form actions. Ported Sverdle to PHP. 28 | -------------------------------------------------------------------------------- /example/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basuke/php-backend", 3 | "autoload": { 4 | "psr-4": { 5 | "App\\": "src/php/" 6 | } 7 | }, 8 | "authors": [ 9 | { 10 | "name": "Basuke Suzuki", 11 | "email": "basuke@mac.com" 12 | } 13 | ], 14 | "repositories": [ 15 | { 16 | "type": "path", 17 | "url": ".." 18 | } 19 | ], 20 | "require": { 21 | "basuke/sveltekit-php-backend": "^1.0", 22 | "alrik11es/cowsayphp": "^1.2", 23 | "mdwheele/fortune": "dev-master" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-backend", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "prepare": "composer install" 12 | }, 13 | "devDependencies": { 14 | "@fontsource/fira-mono": "^5.0.3", 15 | "@neoconfetti/svelte": "^1.0.0", 16 | "@sveltejs/adapter-auto": "^2.0.0", 17 | "@sveltejs/adapter-node": "^1.2.2", 18 | "@sveltejs/kit": "^1.16.2", 19 | "@types/cookie": "^0.5.1", 20 | "cookie": "^0.5.0", 21 | "svelte": "^3.54.0", 22 | "svelte-check": "^3.0.1", 23 | "tslib": "^2.4.1", 24 | "typescript": "^5.1.3", 25 | "vite": "^4.0.0", 26 | "vite-plugin-sveltekit-php-backend": "^0.5" 27 | }, 28 | "type": "module", 29 | "dependencies": {} 30 | } 31 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /example/src/lib/images/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 16 | -------------------------------------------------------------------------------- /example/src/lib/images/svelte-logo.svg: -------------------------------------------------------------------------------- 1 | svelte-logo -------------------------------------------------------------------------------- /example/src/lib/images/svelte-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/basuke/vite-plugin-sveltekit-php-backend/3a23a45ccb4e26a8d6e98809d1221b7983c3a379/example/src/lib/images/svelte-welcome.png -------------------------------------------------------------------------------- /example/src/lib/images/svelte-welcome.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/basuke/vite-plugin-sveltekit-php-backend/3a23a45ccb4e26a8d6e98809d1221b7983c3a379/example/src/lib/images/svelte-welcome.webp -------------------------------------------------------------------------------- /example/src/php/Game.php: -------------------------------------------------------------------------------- 1 | index = intval($index); 24 | $this->guesses = $guesses ? explode(' ', $guesses) : []; 25 | $this->answers = $answers ? explode(' ', $answers) : []; 26 | } else { 27 | $this->index = rand(0, count($words) - 1); 28 | $this->guesses = ['', '', '', '', '', '']; 29 | $this->answers = []; 30 | } 31 | 32 | $this->answer = $words[$this->index]; 33 | } 34 | 35 | /** 36 | * Update game state based on a guess of a five-letter word. Returns 37 | * true if the guess was valid, false otherwise 38 | * @param {string[]} letters 39 | */ 40 | public function enter($letters) { 41 | $word = implode('', $letters); 42 | $valid = array_search($word, Words::allowed()); 43 | 44 | if ($valid === false) return false; 45 | 46 | $this->guesses[count($this->answers)] = $word; 47 | 48 | $available = "" . $this->answer; 49 | $answer = str_repeat('_', 5); 50 | 51 | // first, find exact matches 52 | for ($i = 0; $i < 5; $i += 1) { 53 | if ($letters[$i] === $available[$i]) { 54 | $answer[$i] = 'x'; 55 | $available[$i] = ' '; 56 | } 57 | } 58 | 59 | // then find close matches (this has to happen 60 | // in a second step, otherwise an early close 61 | // match can prevent a later exact match) 62 | for ($i = 0; $i < 5; $i += 1) { 63 | if ($answer[$i] === '_') { 64 | $index = strpos($available, $letters[$i]); 65 | if ($index !== false) { 66 | $answer[$i] = 'c'; 67 | $available[$index] = ' '; 68 | } 69 | } 70 | } 71 | 72 | $this->answers[] = $answer; 73 | 74 | return true; 75 | } 76 | 77 | /** 78 | * Serialize game state so it can be set as a cookie 79 | */ 80 | public function __toString() 81 | { 82 | return implode('-', [$this->index, implode(' ', $this->guesses), implode(' ', $this->answers)]); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /example/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 | 9 |
10 | 11 |
12 | 13 | 16 |
17 | 18 | 54 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/src/routes/Header.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 | 12 | SvelteKit 13 | 14 | + 15 | 16 | PHP 17 | 18 |
19 | 20 | 42 | 43 |
44 | 45 | GitHub 46 | 47 |
48 |
49 | 50 | 137 | -------------------------------------------------------------------------------- /example/src/routes/cowsay/+page.server.php: -------------------------------------------------------------------------------- 1 | say($fortune); 21 | 22 | return [ 23 | 'lines' => explode("\n", $art) 24 | ]; 25 | } -------------------------------------------------------------------------------- /example/src/routes/cowsay/+page.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
{#each data.lines as line}{line}
{/each}
6 | 7 | -------------------------------------------------------------------------------- /example/src/routes/pherdle/+page.server.php: -------------------------------------------------------------------------------- 1 | $game->guesses, 14 | 15 | /** 16 | * An array of strings like '__x_c' corresponding to the guesses, where 'x' means 17 | * an exact match, and 'c' means a close match (right letter, wrong place) 18 | */ 19 | 'answers' => $game->answers, 20 | 21 | /** 22 | * The correct answer, revealed if the game is over 23 | */ 24 | 'answer' => count($game->answers) >= 6 ? $game->answer : null 25 | ]; 26 | } 27 | 28 | $actions = [ 29 | /** 30 | * Modify game state in reaction to a keypress. If client-side JavaScript 31 | * is available, this will happen in the browser instead of here 32 | */ 33 | 'update' => function ($event) { 34 | $game = new Game($_COOKIE['sverdle']); 35 | 36 | $key = $event->post['key']; 37 | 38 | $i = count($game->answers); 39 | 40 | if ($key === 'backspace') { 41 | if ($game->guesses[$i] !== '') { 42 | $game->guesses[$i] = substr($game->guesses[$i], 0, strlen($game->guesses[$i]) - 1); 43 | } 44 | } else { 45 | $game->guesses[$i] .= $key; 46 | } 47 | 48 | setcookie('sverdle', strval($game)); 49 | }, 50 | 51 | /** 52 | * Modify game state in reaction to a guessed word. This logic always runs on 53 | * the server, so that people can't cheat by peeking at the JavaScript 54 | */ 55 | 'enter' => function ($event) { 56 | $game = new Game($_COOKIE['sverdle']); 57 | 58 | $guess = $event->post->getAll('guess'); 59 | 60 | if (!$game->enter($guess)) { 61 | return fail(400, ['badGuess' => true ]); 62 | } 63 | 64 | setcookie('sverdle', strval($game)); 65 | }, 66 | 67 | 'restart' => function ($event) { 68 | setcookie('sverdle', ''); 69 | }, 70 | ]; 71 | -------------------------------------------------------------------------------- /example/src/routes/pherdle/+page.svelte: -------------------------------------------------------------------------------- 1 | 87 | 88 | 89 | 90 | 91 | Sverdle 92 | 93 | 94 | 95 |

Sverdle

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

Row {row + 1}

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

the answer was "{data.answer}"

145 | {/if} 146 | 149 | {:else} 150 |
151 | 152 | 153 | 162 | 163 | {#each ['qwertyuiop', 'asdfghjkl', 'zxcvbnm'] as row} 164 |
165 | {#each row as letter} 166 | 178 | {/each} 179 |
180 | {/each} 181 |
182 | {/if} 183 |
184 |
185 | 186 | {#if won} 187 |
197 | {/if} 198 | 199 | 411 | -------------------------------------------------------------------------------- /example/src/routes/pherdle/how-to-play/+page.js: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /example/src/routes/pherdle/how-to-play/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | How to play Pherdle 3 | 4 | 5 | 6 |
7 |

How to play Pherdle

8 | 9 |

10 | Pherdle is port of Swerdle which 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, Pherdle 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 | -------------------------------------------------------------------------------- /example/src/routes/pherdle/reduced-motion.js: -------------------------------------------------------------------------------- 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 | /** 14 | * @param {MediaQueryListEvent} event 15 | */ 16 | const set_reduced_motion = (event) => { 17 | set(event.matches); 18 | }; 19 | const media_query_list = window.matchMedia(reduced_motion_query); 20 | media_query_list.addEventListener('change', set_reduced_motion); 21 | 22 | return () => { 23 | media_query_list.removeEventListener('change', set_reduced_motion); 24 | }; 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /example/src/routes/php/+page.svelte: -------------------------------------------------------------------------------- 1 |

Fruite

2 | 3 |
4 | Apple | 5 | Orange | 6 | Kiwi 7 |
8 |
Example of passing route params to backend PHP.
9 | 10 | -------------------------------------------------------------------------------- /example/src/routes/php/[fruite]/+page.js: -------------------------------------------------------------------------------- 1 | import { error } from '@sveltejs/kit'; 2 | 3 | export async function load({ params, data, fetch }) { 4 | const res = await fetch(`/php/emoji/${params.fruite}`); 5 | try { 6 | const result = await res.json(); 7 | return { ...(data ?? {}), emoji: result.emoji }; 8 | } catch (e) { 9 | throw error(500, 'Invalid JSON'); 10 | } 11 | } -------------------------------------------------------------------------------- /example/src/routes/php/[fruite]/+page.server.php: -------------------------------------------------------------------------------- 1 | params['fruite'] ?? 'unknown'; 7 | 8 | $lastChoise = $_COOKIE['fruite']; 9 | setcookie('fruite', $fruite); 10 | 11 | return [ 12 | 'name' => Ucfirst($fruite), 13 | 'previous' => ucfirst($lastChoise), 14 | 'encoded' => serialize($fruite), 15 | 'server' => $_SERVER, 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /example/src/routes/php/[fruite]/+page.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |

Hello {data.name} {data.emoji}

15 | 16 | Go back 17 | 18 |

Check the content of `routes/php/[fruite]/+page.server.php`.

19 | 20 |
21 | 22 |

Cookie support

23 | {#if data.previous} 24 |

Last choise was {#if data.previous == data.name}also {/if}{data.previous}

25 | {/if} 26 | 27 |
28 | 29 |

PHP seriarized name is

30 |

Result of `serialize($fruite)`.

31 |
{data.encoded}
32 | 33 |
34 | 35 |

$_SERVER

36 |

These values are returned from PHP's $_SERVER.

37 |
38 | {#each Object.keys(data.server) as key}
39 |     {key} = {data.server[key]}
40 | {/each} 41 |
42 | 43 | 63 | -------------------------------------------------------------------------------- /example/src/routes/php/emoji/[fruite]/+server.php: -------------------------------------------------------------------------------- 1 | params['fruite']; 8 | 9 | switch ($fruite) { 10 | case 'apple': 11 | $emoji = '🍎'; 12 | break; 13 | case 'orange': 14 | $emoji = '🍊'; 15 | break; 16 | default: 17 | $emoji = '🤷🏻‍♂️'; 18 | break; 19 | } 20 | 21 | json([ 22 | 'fruite' => $fruite, 23 | 'emoji' => $emoji, 24 | ]); 25 | } 26 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/basuke/vite-plugin-sveltekit-php-backend/3a23a45ccb4e26a8d6e98809d1221b7983c3a379/example/static/favicon.png -------------------------------------------------------------------------------- /example/static/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /example/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-node'; 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: adapter(), 12 | moduleExtensions: ['.ts', '.js', '.php'], 13 | } 14 | }; 15 | 16 | export default config; 17 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import phpBackend from 'vite-plugin-sveltekit-php-backend'; 3 | import { defineConfig } from 'vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [sveltekit(), phpBackend({ address: 'localhost:9000' })], 7 | }); 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-plugin-sveltekit-php-backend", 3 | "version": "0.6.0", 4 | "description": "PHP Backend Vite Plugin for SvelteKit", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "type": "module", 8 | "files": [ 9 | "/dist" 10 | ], 11 | "scripts": { 12 | "build": "tsc", 13 | "watch": "tsc -w", 14 | "prepublishOnly": "npm run build" 15 | }, 16 | "author": "Basuke Suzuki", 17 | "license": "MIT", 18 | "dependencies": { 19 | "fastcgi-kit": "^0.16", 20 | "vite": "^4.2.1" 21 | }, 22 | "devDependencies": { 23 | "@sveltejs/kit": "^1.16.2", 24 | "@types/node": "^20.3.0", 25 | "typescript": "^5.0.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /playground/index.php: -------------------------------------------------------------------------------- 1 | function (string $url, array $params) { 37 | 38 | }, 39 | ]; 40 | 41 | \Basuke\SvelteKit\Backend::main(__NAMESPACE__); 42 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vite'; 2 | 3 | import path from 'node:path'; 4 | import fs from 'node:fs'; 5 | import url from 'node:url'; 6 | import { createClient } from 'fastcgi-kit'; 7 | 8 | const name = 'vite-plugin-sveltekit-php-backend'; 9 | const sharedClientVirtualModule = `virtual:${name}/shared-client`; 10 | const sharedClientResolvedModuleId = '\0' + sharedClientVirtualModule; 11 | const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); 12 | const runtimeCode = fs.readFileSync(path.join(__dirname, 'runtime.js'), 'utf8'); 13 | 14 | const verbose = { 15 | showCodeStructure: false, 16 | showGeneratedJSCode: false, 17 | }; 18 | 19 | interface PHPBackendOptions { 20 | address?: string; 21 | debug?: boolean; 22 | } 23 | 24 | type ParameterSpec = [string, string]; // [type, name] 25 | type FunctionSpec = ParameterSpec[]; 26 | 27 | interface CodeStructure { 28 | load: FunctionSpec | null; 29 | endpoints: Record; 30 | actions: Record; 31 | } 32 | 33 | function posixify(str: string): string { 34 | return str.replace(/\\/g, '/'); 35 | } 36 | 37 | function flattenId(id: string): string { 38 | return posixify(id).replaceAll('/', '_'); 39 | } 40 | 41 | function Q(s: string): string { 42 | return JSON.stringify(s); 43 | } 44 | 45 | /** 46 | * Returns the PHP Backend for SvelteKit Vite plugin. 47 | */ 48 | export async function plugin(options: PHPBackendOptions = {}): Promise { 49 | const address = options.address ?? 'localhost:9000'; 50 | const debug = options.debug ?? false; 51 | let root: string; 52 | let building = false; 53 | let phpDir: string; 54 | let sharedCode: string; 55 | 56 | const plugin: Plugin = { 57 | name, 58 | 59 | configResolved(config) { 60 | root = config.root; 61 | building = config.command === 'build'; 62 | phpDir = path.join(config.cacheDir, 'php'); 63 | sharedCode = runtimeCode 64 | .replace("'%ADDRESS%'", Q(address)) 65 | .replace("'%DOCUMENT_ROOT%'", Q(root)); 66 | 67 | fs.mkdirSync(phpDir, { recursive: true }); 68 | }, 69 | 70 | buildStart() {}, 71 | 72 | resolveId(id) { 73 | if (id === sharedClientVirtualModule) { 74 | return sharedClientResolvedModuleId; 75 | } 76 | 77 | // Error check for client side logic. 78 | if (id.endsWith('.php')) { 79 | if (id !== '+server.php' && !id.endsWith('.server.php')) { 80 | throw new Error('PHP cannot run on client side.'); 81 | } 82 | } 83 | }, 84 | 85 | load(id) { 86 | if (id === sharedClientResolvedModuleId) { 87 | return sharedCode; 88 | } 89 | }, 90 | 91 | async transform(code, id) { 92 | if (id.endsWith('.php')) { 93 | id = path.relative(root, id); 94 | 95 | const phpPath = path.join(phpDir, flattenId(id)); 96 | fs.writeFileSync(phpPath, phpBackendMain + code); 97 | 98 | const relativePath = path.relative(root, phpPath); 99 | 100 | const structure = await analyzeCodeStructure( 101 | address, 102 | root, 103 | relativePath 104 | ); 105 | if (verbose.showCodeStructure) { 106 | console.log(structure); 107 | } 108 | 109 | if (id.endsWith('+server.php')) { 110 | code = invokePhpEndpointJS(relativePath, structure); 111 | } else { 112 | code = 113 | invokePhpLoadJS(relativePath, structure) + 114 | definePhpActionsJS(relativePath, structure); 115 | } 116 | if (verbose.showGeneratedJSCode) { 117 | code.split('\n').forEach((line) => console.log(line)); 118 | } 119 | return { code }; 120 | } 121 | }, 122 | buildEnd() {}, 123 | }; 124 | return plugin; 125 | } 126 | 127 | async function analyzeCodeStructure( 128 | address: string, 129 | root: string, 130 | phpPath: string 131 | ): Promise { 132 | const client = createClient({ 133 | address, 134 | params: { 135 | DOCUMENT_ROOT: root, 136 | }, 137 | }); 138 | 139 | const backendUrl = new URL('http://localhost/' + phpPath); 140 | const response = await client.options(backendUrl, {}); 141 | const config = response.json(); 142 | 143 | const endpoints: Record = {}; 144 | const actions: Record = {}; 145 | let load: FunctionSpec | null = null; 146 | 147 | const filterParam = (arg): ParameterSpec | null => { 148 | if (Array.isArray(arg) && arg.length === 2) { 149 | const [type, name] = arg; 150 | return [type, name]; 151 | } else { 152 | return null; 153 | } 154 | }; 155 | 156 | const filterFunc = (arg): FunctionSpec | null => { 157 | if (Array.isArray(arg)) { 158 | return arg.map(filterParam).filter((x) => x !== null); 159 | } else { 160 | return null; 161 | } 162 | }; 163 | 164 | if (typeof config === 'object') { 165 | if (config.load) { 166 | load = filterFunc(config.load); 167 | } 168 | if (config.endpoints && typeof config.endpoints === 'object') { 169 | for (const method in config.endpoints) { 170 | const spec = filterFunc(config.endpoints[method]); 171 | if (spec) { 172 | endpoints[method] = spec; 173 | } 174 | } 175 | } 176 | if (config.actions && typeof config.actions === 'object') { 177 | for (const action in config.actions) { 178 | const spec = filterFunc(config.actions[action]); 179 | if (spec) { 180 | actions[action] = spec; 181 | } 182 | } 183 | } 184 | } 185 | 186 | return { 187 | load, 188 | endpoints, 189 | actions, 190 | }; 191 | } 192 | 193 | const invokePhpLoadJS = (phpPath: string, structure: CodeStructure) => { 194 | if (!structure.load) { 195 | return ''; 196 | } 197 | 198 | return ` 199 | import { invokePhpLoad } from "${sharedClientVirtualModule}"; 200 | 201 | export const load = (async (event) => { 202 | return await invokePhpLoad(${Q(phpPath)}, event); 203 | }); 204 | `; 205 | }; 206 | 207 | const invokePhpEndpointJS = (phpPath: string, structure: CodeStructure) => { 208 | const methods = Object.keys(structure.endpoints); 209 | if (methods.length === 0) { 210 | return ''; 211 | } 212 | 213 | return ( 214 | ` 215 | import { invokePhpEndpoint } from "${sharedClientVirtualModule}"; 216 | ` + 217 | methods 218 | .map( 219 | (method) => ` 220 | export async function ${method}(event) { 221 | return await invokePhpEndpoint(${Q(phpPath)}, ${Q(method)}, event); 222 | } 223 | ` 224 | ) 225 | .join('\n') 226 | ); 227 | }; 228 | 229 | const definePhpActionsJS = (phpPath: string, structure: CodeStructure) => { 230 | const actions = Object.keys(structure.actions); 231 | if (actions.length === 0) { 232 | return ''; 233 | } 234 | 235 | return ( 236 | ` 237 | import { invokePhpActions } from "${sharedClientVirtualModule}"; 238 | 239 | export const actions = {` + 240 | // prettier-ignore 241 | actions.map((action) => ` 242 | ${Q(action)}: async (event) => invokePhpActions(${Q(phpPath)}, ${Q(action)}, event)`).join(",\n") + 243 | ` 244 | }; 245 | ` 246 | ); 247 | }; 248 | 249 | const phpBackendMain = ``; 256 | 257 | export default plugin; 258 | -------------------------------------------------------------------------------- /src/runtime.ts: -------------------------------------------------------------------------------- 1 | import { fail } from '@sveltejs/kit'; 2 | import Cookie from 'cookie'; 3 | import { createClient } from 'fastcgi-kit'; 4 | 5 | export const client = createClient({ 6 | address: '%ADDRESS%', 7 | debug: false, 8 | params: { 9 | DOCUMENT_ROOT: '%DOCUMENT_ROOT%', 10 | }, 11 | }); 12 | 13 | function createFCGIParams({ params, route, url, cookies, request }) { 14 | const fcgiParams = { 15 | SVELTEKIT_PAGESERVEREVENT: JSON.stringify({ 16 | params, 17 | route, 18 | url: url.toString(), 19 | }), 20 | }; 21 | 22 | // add cookie params if cookie exists. 23 | const allCookies = cookies.getAll(); 24 | if (allCookies.length) { 25 | fcgiParams['HTTP_COOKIE'] = allCookies 26 | .map(({ name, value }) => Cookie.serialize(name, value)) 27 | .join('; '); 28 | } 29 | 30 | return fcgiParams; 31 | } 32 | 33 | function forwardCookies(response, { cookies }) { 34 | const setCookieHeader = response.headers['set-cookie']; 35 | if (setCookieHeader) { 36 | const parsed = Cookie.parse(setCookieHeader); 37 | for (const name in parsed) { 38 | const value = parsed[name]; 39 | if (value === 'deleted') { 40 | cookies.delete(name); 41 | } else { 42 | cookies.set(name, value); 43 | } 44 | break; 45 | } 46 | } 47 | } 48 | 49 | export const invokePhpLoad = async (path, event) => { 50 | return new Promise(async (resolve, reject) => { 51 | const backendUrl = new URL('http://localhost/' + path); 52 | const fcgiParams = createFCGIParams(event); 53 | 54 | try { 55 | const response = await client.get(backendUrl, fcgiParams); 56 | // console.log(response); 57 | forwardCookies(response, event); 58 | resolve(response.json()); 59 | } catch (e) { 60 | reject(e); 61 | } 62 | }); 63 | }; 64 | 65 | export const invokePhpEndpoint = async (path, method, event) => { 66 | return new Promise(async (resolve, reject) => { 67 | const backendUrl = new URL('http://localhost/' + path); 68 | const fcgiParams = createFCGIParams(event); 69 | fcgiParams['SVELTEKIT_METHOD'] = method; 70 | 71 | try { 72 | const response = await client.request({ 73 | url: backendUrl, 74 | method: method as string, 75 | params: fcgiParams, 76 | }); 77 | // console.log(response); 78 | forwardCookies(response, event); 79 | 80 | const res = new Response(response.body, { 81 | status: response.statusCode, 82 | headers: response.headers, 83 | }); 84 | resolve(res); 85 | } catch (e) { 86 | reject(e); 87 | } 88 | }); 89 | }; 90 | 91 | export async function invokePhpActions(path, action, event) { 92 | const request = event.request; 93 | const body = new URLSearchParams(await request.formData()).toString(); 94 | 95 | return new Promise(async (resolve, reject) => { 96 | const backendUrl = new URL('http://localhost/' + path); 97 | const fcgiParams = createFCGIParams(event); 98 | fcgiParams['SVELTEKIT_ACTION'] = action; 99 | 100 | try { 101 | const response = await client.post(backendUrl, body, fcgiParams); 102 | if (response.statusCode >= 400) { 103 | resolve(fail(response.statusCode, response.json())); 104 | } 105 | forwardCookies(response, event); 106 | resolve(null); 107 | } catch (e) { 108 | reject(e); 109 | } 110 | }); 111 | } 112 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "isolatedModules": true, 4 | "lib": ["esnext", "dom"], 5 | "moduleResolution": "node", 6 | "module": "esnext", 7 | "target": "esnext", 8 | "declaration": true, 9 | "preserveValueImports": true, 10 | "outDir": "dist/", 11 | "ignoreDeprecations": "5.0", 12 | "allowSyntheticDefaultImports": true 13 | }, 14 | "include": ["./src/**/*.js", "./src/**/*.ts"], 15 | "exclude": ["./node_modules/**"] 16 | } 17 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@esbuild/android-arm64@0.17.19": 6 | version "0.17.19" 7 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd" 8 | integrity sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA== 9 | 10 | "@esbuild/android-arm@0.17.19": 11 | version "0.17.19" 12 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d" 13 | integrity sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A== 14 | 15 | "@esbuild/android-x64@0.17.19": 16 | version "0.17.19" 17 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1" 18 | integrity sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww== 19 | 20 | "@esbuild/darwin-arm64@0.17.19": 21 | version "0.17.19" 22 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz#584c34c5991b95d4d48d333300b1a4e2ff7be276" 23 | integrity sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg== 24 | 25 | "@esbuild/darwin-x64@0.17.19": 26 | version "0.17.19" 27 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb" 28 | integrity sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw== 29 | 30 | "@esbuild/freebsd-arm64@0.17.19": 31 | version "0.17.19" 32 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2" 33 | integrity sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ== 34 | 35 | "@esbuild/freebsd-x64@0.17.19": 36 | version "0.17.19" 37 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4" 38 | integrity sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ== 39 | 40 | "@esbuild/linux-arm64@0.17.19": 41 | version "0.17.19" 42 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb" 43 | integrity sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg== 44 | 45 | "@esbuild/linux-arm@0.17.19": 46 | version "0.17.19" 47 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a" 48 | integrity sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA== 49 | 50 | "@esbuild/linux-ia32@0.17.19": 51 | version "0.17.19" 52 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a" 53 | integrity sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ== 54 | 55 | "@esbuild/linux-loong64@0.17.19": 56 | version "0.17.19" 57 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72" 58 | integrity sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ== 59 | 60 | "@esbuild/linux-mips64el@0.17.19": 61 | version "0.17.19" 62 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289" 63 | integrity sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A== 64 | 65 | "@esbuild/linux-ppc64@0.17.19": 66 | version "0.17.19" 67 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7" 68 | integrity sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg== 69 | 70 | "@esbuild/linux-riscv64@0.17.19": 71 | version "0.17.19" 72 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09" 73 | integrity sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA== 74 | 75 | "@esbuild/linux-s390x@0.17.19": 76 | version "0.17.19" 77 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829" 78 | integrity sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q== 79 | 80 | "@esbuild/linux-x64@0.17.19": 81 | version "0.17.19" 82 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4" 83 | integrity sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw== 84 | 85 | "@esbuild/netbsd-x64@0.17.19": 86 | version "0.17.19" 87 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462" 88 | integrity sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q== 89 | 90 | "@esbuild/openbsd-x64@0.17.19": 91 | version "0.17.19" 92 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691" 93 | integrity sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g== 94 | 95 | "@esbuild/sunos-x64@0.17.19": 96 | version "0.17.19" 97 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273" 98 | integrity sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg== 99 | 100 | "@esbuild/win32-arm64@0.17.19": 101 | version "0.17.19" 102 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f" 103 | integrity sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag== 104 | 105 | "@esbuild/win32-ia32@0.17.19": 106 | version "0.17.19" 107 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03" 108 | integrity sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw== 109 | 110 | "@esbuild/win32-x64@0.17.19": 111 | version "0.17.19" 112 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061" 113 | integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA== 114 | 115 | "@jridgewell/sourcemap-codec@^1.4.13": 116 | version "1.4.15" 117 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" 118 | integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== 119 | 120 | "@polka/url@^1.0.0-next.20": 121 | version "1.0.0-next.21" 122 | resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" 123 | integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== 124 | 125 | "@sveltejs/kit@^1.16.2": 126 | version "1.20.2" 127 | resolved "https://registry.yarnpkg.com/@sveltejs/kit/-/kit-1.20.2.tgz#4167cdc04387c67216b7b3d5875c3553c7315e78" 128 | integrity sha512-MtR1i+HtmYWcRgtubw1GQqT/+CWXL/z24PegE0xYAdObbhdr7YtEfmoe705D/JZMtMmoPXrmSk4W0MfL5A3lYw== 129 | dependencies: 130 | "@sveltejs/vite-plugin-svelte" "^2.4.1" 131 | "@types/cookie" "^0.5.1" 132 | cookie "^0.5.0" 133 | devalue "^4.3.1" 134 | esm-env "^1.0.0" 135 | kleur "^4.1.5" 136 | magic-string "^0.30.0" 137 | mime "^3.0.0" 138 | sade "^1.8.1" 139 | set-cookie-parser "^2.6.0" 140 | sirv "^2.0.2" 141 | tiny-glob "^0.2.9" 142 | undici "~5.22.0" 143 | 144 | "@sveltejs/vite-plugin-svelte-inspector@^1.0.2": 145 | version "1.0.2" 146 | resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.2.tgz#da51dd8da54e69a706ad4793103f90aedae2c481" 147 | integrity sha512-Cy1dUMcYCnDVV/hPLXa43YZJ2jGKVW5rA0xuNL9dlmYhT0yoS1g7+FOFSRlgk0BXKk/Oc7grs+8BVA5Iz2fr8A== 148 | dependencies: 149 | debug "^4.3.4" 150 | 151 | "@sveltejs/vite-plugin-svelte@^2.4.1": 152 | version "2.4.1" 153 | resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.1.tgz#bab9d97a83fee641562f2657ce6ea755808bc149" 154 | integrity sha512-bNNKvoRY89ptY7udeBSCmTdCVwkjmMcZ0j/z9J5MuedT8jPjq0zrknAo/jF1sToAza4NVaAgR9AkZoD9oJJmnA== 155 | dependencies: 156 | "@sveltejs/vite-plugin-svelte-inspector" "^1.0.2" 157 | debug "^4.3.4" 158 | deepmerge "^4.3.1" 159 | kleur "^4.1.5" 160 | magic-string "^0.30.0" 161 | svelte-hmr "^0.15.1" 162 | vitefu "^0.2.4" 163 | 164 | "@types/cookie@^0.5.1": 165 | version "0.5.1" 166 | resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.5.1.tgz#b29aa1f91a59f35e29ff8f7cb24faf1a3a750554" 167 | integrity sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g== 168 | 169 | "@types/node@^20.3.0": 170 | version "20.3.0" 171 | resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.0.tgz#719498898d5defab83c3560f45d8498f58d11938" 172 | integrity sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ== 173 | 174 | busboy@^1.6.0: 175 | version "1.6.0" 176 | resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" 177 | integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== 178 | dependencies: 179 | streamsearch "^1.1.0" 180 | 181 | "chainsaw@>=0.0.7 <0.1": 182 | version "0.0.9" 183 | resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.0.9.tgz#11a05102d1c4c785b6d0415d336d5a3a1612913e" 184 | integrity sha512-nG8PYH+/4xB+8zkV4G844EtfvZ5tTiLFoX3dZ4nhF4t3OCKIb9UvaFyNmeZO2zOSmRWzBoTD+napN6hiL+EgcA== 185 | dependencies: 186 | traverse ">=0.3.0 <0.4" 187 | 188 | cookie@^0.5.0: 189 | version "0.5.0" 190 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" 191 | integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== 192 | 193 | debug@^4.3.4: 194 | version "4.3.4" 195 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 196 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 197 | dependencies: 198 | ms "2.1.2" 199 | 200 | deepmerge@^4.3.1: 201 | version "4.3.1" 202 | resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" 203 | integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== 204 | 205 | devalue@^4.3.1: 206 | version "4.3.2" 207 | resolved "https://registry.yarnpkg.com/devalue/-/devalue-4.3.2.tgz#cc44e4cf3872ac5a78229fbce3b77e57032727b5" 208 | integrity sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg== 209 | 210 | esbuild@^0.17.5: 211 | version "0.17.19" 212 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.19.tgz#087a727e98299f0462a3d0bcdd9cd7ff100bd955" 213 | integrity sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw== 214 | optionalDependencies: 215 | "@esbuild/android-arm" "0.17.19" 216 | "@esbuild/android-arm64" "0.17.19" 217 | "@esbuild/android-x64" "0.17.19" 218 | "@esbuild/darwin-arm64" "0.17.19" 219 | "@esbuild/darwin-x64" "0.17.19" 220 | "@esbuild/freebsd-arm64" "0.17.19" 221 | "@esbuild/freebsd-x64" "0.17.19" 222 | "@esbuild/linux-arm" "0.17.19" 223 | "@esbuild/linux-arm64" "0.17.19" 224 | "@esbuild/linux-ia32" "0.17.19" 225 | "@esbuild/linux-loong64" "0.17.19" 226 | "@esbuild/linux-mips64el" "0.17.19" 227 | "@esbuild/linux-ppc64" "0.17.19" 228 | "@esbuild/linux-riscv64" "0.17.19" 229 | "@esbuild/linux-s390x" "0.17.19" 230 | "@esbuild/linux-x64" "0.17.19" 231 | "@esbuild/netbsd-x64" "0.17.19" 232 | "@esbuild/openbsd-x64" "0.17.19" 233 | "@esbuild/sunos-x64" "0.17.19" 234 | "@esbuild/win32-arm64" "0.17.19" 235 | "@esbuild/win32-ia32" "0.17.19" 236 | "@esbuild/win32-x64" "0.17.19" 237 | 238 | esm-env@^1.0.0: 239 | version "1.0.0" 240 | resolved "https://registry.yarnpkg.com/esm-env/-/esm-env-1.0.0.tgz#b124b40b180711690a4cb9b00d16573391950413" 241 | integrity sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA== 242 | 243 | fastcgi-kit@^0.16: 244 | version "0.16.0" 245 | resolved "https://registry.yarnpkg.com/fastcgi-kit/-/fastcgi-kit-0.16.0.tgz#84be69a7241b859c69a46d77a0a31ed7a82cf162" 246 | integrity sha512-MfSEpsozUa891AbhJsm0U+60JWpzlAP9SIv/cPV/2bdOFnrCapz5guYvFlI813flkVpEEBh3J6eWygIYsjJGrA== 247 | dependencies: 248 | remove "^0.1.5" 249 | 250 | fsevents@~2.3.2: 251 | version "2.3.2" 252 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 253 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 254 | 255 | globalyzer@0.1.0: 256 | version "0.1.0" 257 | resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465" 258 | integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q== 259 | 260 | globrex@^0.1.2: 261 | version "0.1.2" 262 | resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" 263 | integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== 264 | 265 | "hashish@>=0.0.2 <0.1": 266 | version "0.0.4" 267 | resolved "https://registry.yarnpkg.com/hashish/-/hashish-0.0.4.tgz#6d60bc6ffaf711b6afd60e426d077988014e6554" 268 | integrity sha512-xyD4XgslstNAs72ENaoFvgMwtv8xhiDtC2AtzCG+8yF7W/Knxxm9BX+e2s25mm+HxMKh0rBmXVOEGF3zNImXvA== 269 | dependencies: 270 | traverse ">=0.2.4" 271 | 272 | kleur@^4.1.5: 273 | version "4.1.5" 274 | resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" 275 | integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== 276 | 277 | magic-string@^0.30.0: 278 | version "0.30.0" 279 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.0.tgz#fd58a4748c5c4547338a424e90fa5dd17f4de529" 280 | integrity sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ== 281 | dependencies: 282 | "@jridgewell/sourcemap-codec" "^1.4.13" 283 | 284 | mime@^3.0.0: 285 | version "3.0.0" 286 | resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" 287 | integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== 288 | 289 | mri@^1.1.0: 290 | version "1.2.0" 291 | resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" 292 | integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== 293 | 294 | mrmime@^1.0.0: 295 | version "1.0.1" 296 | resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" 297 | integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== 298 | 299 | ms@2.1.2: 300 | version "2.1.2" 301 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 302 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 303 | 304 | nanoid@^3.3.6: 305 | version "3.3.6" 306 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" 307 | integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== 308 | 309 | picocolors@^1.0.0: 310 | version "1.0.0" 311 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 312 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 313 | 314 | postcss@^8.4.23: 315 | version "8.4.24" 316 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.24.tgz#f714dba9b2284be3cc07dbd2fc57ee4dc972d2df" 317 | integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg== 318 | dependencies: 319 | nanoid "^3.3.6" 320 | picocolors "^1.0.0" 321 | source-map-js "^1.0.2" 322 | 323 | remove@^0.1.5: 324 | version "0.1.5" 325 | resolved "https://registry.yarnpkg.com/remove/-/remove-0.1.5.tgz#095ffd827d65c9f41ad97d33e416a75811079955" 326 | integrity sha512-AJMA9oWvJzdTjwIGwSQZsjGQiRx73YTmiOWmfCp1fpLa/D4n7jKcpoA+CZiVLJqKcEKUuh1Suq80c5wF+L/qVQ== 327 | dependencies: 328 | seq ">= 0.3.5" 329 | 330 | rollup@^3.21.0: 331 | version "3.25.0" 332 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.25.0.tgz#71327d396a9decbf23c87b55916ae7204211738a" 333 | integrity sha512-FnJkNRst2jEZGw7f+v4hFo6UTzpDKrAKcHZWcEfm5/GJQ5CK7wgb4moNLNAe7npKUev7yQn1AY/YbZRIxOv6Qg== 334 | optionalDependencies: 335 | fsevents "~2.3.2" 336 | 337 | sade@^1.8.1: 338 | version "1.8.1" 339 | resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" 340 | integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== 341 | dependencies: 342 | mri "^1.1.0" 343 | 344 | "seq@>= 0.3.5": 345 | version "0.3.5" 346 | resolved "https://registry.yarnpkg.com/seq/-/seq-0.3.5.tgz#ae02af3a424793d8ccbf212d69174e0c54dffe38" 347 | integrity sha512-sisY2Ln1fj43KBkRtXkesnRHYNdswIkIibvNe/0UKm2GZxjMbqmccpiatoKr/k2qX5VKiLU8xm+tz/74LAho4g== 348 | dependencies: 349 | chainsaw ">=0.0.7 <0.1" 350 | hashish ">=0.0.2 <0.1" 351 | 352 | set-cookie-parser@^2.6.0: 353 | version "2.6.0" 354 | resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz#131921e50f62ff1a66a461d7d62d7b21d5d15a51" 355 | integrity sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ== 356 | 357 | sirv@^2.0.2: 358 | version "2.0.3" 359 | resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446" 360 | integrity sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA== 361 | dependencies: 362 | "@polka/url" "^1.0.0-next.20" 363 | mrmime "^1.0.0" 364 | totalist "^3.0.0" 365 | 366 | source-map-js@^1.0.2: 367 | version "1.0.2" 368 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" 369 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 370 | 371 | streamsearch@^1.1.0: 372 | version "1.1.0" 373 | resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" 374 | integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== 375 | 376 | svelte-hmr@^0.15.1: 377 | version "0.15.2" 378 | resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.15.2.tgz#d2f6fc82e040f2734abd54cea5cbef46828f2538" 379 | integrity sha512-q/bAruCvFLwvNbeE1x3n37TYFb3mTBJ6TrCq6p2CoFbSTNhDE9oAtEfpy+wmc9So8AG0Tja+X0/mJzX9tSfvIg== 380 | 381 | tiny-glob@^0.2.9: 382 | version "0.2.9" 383 | resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" 384 | integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg== 385 | dependencies: 386 | globalyzer "0.1.0" 387 | globrex "^0.1.2" 388 | 389 | totalist@^3.0.0: 390 | version "3.0.1" 391 | resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" 392 | integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== 393 | 394 | traverse@>=0.2.4: 395 | version "0.6.7" 396 | resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.7.tgz#46961cd2d57dd8706c36664acde06a248f1173fe" 397 | integrity sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg== 398 | 399 | "traverse@>=0.3.0 <0.4": 400 | version "0.3.9" 401 | resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" 402 | integrity sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ== 403 | 404 | typescript@^5.0.3: 405 | version "5.1.3" 406 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" 407 | integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== 408 | 409 | undici@~5.22.0: 410 | version "5.22.1" 411 | resolved "https://registry.yarnpkg.com/undici/-/undici-5.22.1.tgz#877d512effef2ac8be65e695f3586922e1a57d7b" 412 | integrity sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw== 413 | dependencies: 414 | busboy "^1.6.0" 415 | 416 | vite@^4.2.1: 417 | version "4.3.9" 418 | resolved "https://registry.yarnpkg.com/vite/-/vite-4.3.9.tgz#db896200c0b1aa13b37cdc35c9e99ee2fdd5f96d" 419 | integrity sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg== 420 | dependencies: 421 | esbuild "^0.17.5" 422 | postcss "^8.4.23" 423 | rollup "^3.21.0" 424 | optionalDependencies: 425 | fsevents "~2.3.2" 426 | 427 | vitefu@^0.2.4: 428 | version "0.2.4" 429 | resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-0.2.4.tgz#212dc1a9d0254afe65e579351bed4e25d81e0b35" 430 | integrity sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g== 431 | --------------------------------------------------------------------------------