├── .nvmrc ├── example ├── .npmrc ├── static │ ├── robots.txt │ └── favicon.png ├── src │ ├── lib │ │ └── images │ │ │ ├── svelte-welcome.png │ │ │ ├── svelte-welcome.webp │ │ │ ├── github.svg │ │ │ └── svelte-logo.svg │ ├── routes │ │ ├── +page.ts │ │ ├── cowsay │ │ │ ├── +page.svelte │ │ │ └── +page.server.php │ │ ├── pherdle │ │ │ ├── how-to-play │ │ │ │ ├── +page.js │ │ │ │ └── +page.svelte │ │ │ ├── reduced-motion.js │ │ │ ├── +page.server.php │ │ │ └── +page.svelte │ │ ├── php │ │ │ ├── [fruite] │ │ │ │ ├── +page.js │ │ │ │ ├── +page.server.php │ │ │ │ └── +page.svelte │ │ │ ├── +page.svelte │ │ │ └── emoji │ │ │ │ └── [fruite] │ │ │ │ └── +server.php │ │ ├── +layout.svelte │ │ ├── +page.svelte │ │ ├── styles.css │ │ ├── Counter.svelte │ │ └── Header.svelte │ ├── app.d.ts │ ├── app.html │ └── php │ │ └── Game.php ├── .gitignore ├── vite.config.ts ├── svelte.config.js ├── composer.json ├── tsconfig.json ├── README.md └── package.json ├── .gitignore ├── .prettierrc ├── .github └── images │ └── svelte+php.gif ├── tsconfig.json ├── package.json ├── playground └── index.php ├── src ├── runtime.ts └── index.ts ├── README.md └── yarn.lock /.nvmrc: -------------------------------------------------------------------------------- 1 | v18 2 | -------------------------------------------------------------------------------- /example/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "semi": true, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /example/static/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /example/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/basuke/vite-plugin-sveltekit-php-backend/HEAD/example/static/favicon.png -------------------------------------------------------------------------------- /.github/images/svelte+php.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/basuke/vite-plugin-sveltekit-php-backend/HEAD/.github/images/svelte+php.gif -------------------------------------------------------------------------------- /example/src/lib/images/svelte-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/basuke/vite-plugin-sveltekit-php-backend/HEAD/example/src/lib/images/svelte-welcome.png -------------------------------------------------------------------------------- /example/src/lib/images/svelte-welcome.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/basuke/vite-plugin-sveltekit-php-backend/HEAD/example/src/lib/images/svelte-welcome.webp -------------------------------------------------------------------------------- /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/.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/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 | -------------------------------------------------------------------------------- /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/routes/cowsay/+page.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
{#each data.lines as line}{line}
{/each}
6 |
7 |
--------------------------------------------------------------------------------
/example/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | Check the content of `routes/php/[fruite]/+page.server.php`.
Last choise was {#if data.previous == data.name}also {/if}{data.previous}
25 | {/if} 26 | 27 |Result of `serialize($fruite)`.
{data.encoded}
32 |
33 | 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/lib/images/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/src/lib/images/svelte-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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/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/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/src/routes/Counter.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 | 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 |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 |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 |
4 |
5 |