From 2f9ad795f3e2ed648345badd96fb2dbcf58a5121 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 09:39:00 +0100 Subject: [PATCH 01/47] Create Vite.php --- packages/framework/src/Facades/Vite.php | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 packages/framework/src/Facades/Vite.php diff --git a/packages/framework/src/Facades/Vite.php b/packages/framework/src/Facades/Vite.php new file mode 100644 index 00000000000..d387e0e58a0 --- /dev/null +++ b/packages/framework/src/Facades/Vite.php @@ -0,0 +1,11 @@ + Date: Tue, 12 Nov 2024 09:39:01 +0100 Subject: [PATCH 02/47] Create ViteFacadeTest.php --- .../framework/tests/Unit/Facades/ViteFacadeTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 packages/framework/tests/Unit/Facades/ViteFacadeTest.php diff --git a/packages/framework/tests/Unit/Facades/ViteFacadeTest.php b/packages/framework/tests/Unit/Facades/ViteFacadeTest.php new file mode 100644 index 00000000000..5ad403733b8 --- /dev/null +++ b/packages/framework/tests/Unit/Facades/ViteFacadeTest.php @@ -0,0 +1,13 @@ + Date: Tue, 12 Nov 2024 09:47:53 +0100 Subject: [PATCH 03/47] Create Vite server configuration --- vite.config.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vite.config.js b/vite.config.js index 6d088d3819a..ff20640ebe1 100644 --- a/vite.config.js +++ b/vite.config.js @@ -8,6 +8,13 @@ import tailwindcss from 'tailwindcss'; import autoprefixer from 'autoprefixer'; export default defineConfig({ + server: { + port: 3000, + hmr: { + host: 'localhost', + port: 3000, + }, + }, css: { postcss: { plugins: [ From 220fceba36cdd8818343bd5ffdee55a69eb2f33b Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 09:48:23 +0100 Subject: [PATCH 04/47] Draft Vite option for the serve command --- docs/getting-started/console-commands.md | 2 +- .../src/Console/Commands/ServeCommand.php | 39 ++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/docs/getting-started/console-commands.md b/docs/getting-started/console-commands.md index 2be9c7de878..f92f196e73b 100644 --- a/docs/getting-started/console-commands.md +++ b/docs/getting-started/console-commands.md @@ -101,7 +101,7 @@ Run the static site builder for a single file ```bash -php hyde serve [--host [HOST]] [--port [PORT]] +php hyde serve [--host [HOST]] [--port [PORT]] [--vite] ``` Start the realtime compiler server. diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index 86008ee805e..2c73e17cd0c 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -35,6 +35,7 @@ class ServeCommand extends Command {--pretty-urls= : Enable pretty URLs. (Overrides config setting)} {--play-cdn= : Enable the Tailwind Play CDN. (Overrides config setting)} {--open=false : Open the site preview in the browser.} + {--vite : Enable Vite for Hot Module Replacement (HMR)} '; /** @var string */ @@ -51,15 +52,45 @@ public function safeHandle(): int $this->openInBrowser((string) $this->option('open')); } - $this->runServerProcess(sprintf('php -S %s:%d %s', + $command = sprintf('php -S %s:%d %s', $this->getHostSelection(), $this->getPortSelection(), $this->getExecutablePath() - )); + ); + + if ($this->option('vite')) { + $this->runViteProcess(); + } + + $this->runServerProcess($command); return Command::SUCCESS; } + protected function runViteProcess(): void + { + $viteCommand = 'npm run dev'; + + Process::forever()->env($this->getViteEnvironmentVariables())->run($viteCommand, $this->getViteOutputHandler()); + + $this->info('Vite development server started for HMR.'); + } + + protected function getViteEnvironmentVariables(): array + { + return Arr::whereNotNull([ + 'NODE_ENV' => 'development', + 'PORT' => 3000, + ]); + } + + protected function getViteOutputHandler(): Closure + { + return function (string $type, string $line): void { + $this->output->write($line); + }; + } + protected function getHostSelection(): string { return (string) $this->option('host') ?: Config::getString('hyde.server.host', 'localhost'); @@ -103,6 +134,10 @@ protected function printStartMessage(): void $this->useBasicOutput() ? $this->output->writeln('Starting the HydeRC server... Press Ctrl+C to stop') : $this->console->printStartMessage($this->getHostSelection(), $this->getPortSelection(), $this->getEnvironmentVariables()); + + if ($this->option('vite')) { + $this->console->printViteMessage(); + } } protected function getOutputHandler(): Closure From d67c699cf995dfb5aa3682707e106d3312c9e487 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 09:48:59 +0100 Subject: [PATCH 05/47] Improve realtime compiler documentation --- docs/extensions/realtime-compiler.md | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/extensions/realtime-compiler.md b/docs/extensions/realtime-compiler.md index 534588803f7..82ff375f736 100644 --- a/docs/extensions/realtime-compiler.md +++ b/docs/extensions/realtime-compiler.md @@ -21,6 +21,21 @@ This will start a local development server at `http://localhost:8080` >warning Please note that the server is designed for local development, and should not be used on a public network. +### Options + +- `--host=`: [default: "localhost"] +- `--port=`: [default: 8080] +- `--save-preview=`: Should the served page be saved to disk? (Overrides config setting) +- `--dashboard=`: Enable the realtime compiler dashboard. (Overrides config setting) +- `--pretty-urls=`: Enable pretty URLs. (Overrides config setting) +- `--play-cdn=`: Enable the Tailwind Play CDN. (Overrides config setting) +- `--open=false`: Open the site preview in the browser. +- `--vite`: Enable Vite for Hot Module Replacement (HMR). + +### Vite Integration + +By adding the `--vite` option, the serve command will initiate Vite's development server alongside the Hyde Realtime Compiler. This setup enables Hot Module Replacement (HMR), allowing for instant updates to your site as you make changes to your assets. + ### Configuration The server can be configured in the `config/hyde.php` file to change the port, host, and to customize its features. @@ -29,9 +44,30 @@ The server can be configured in the `config/hyde.php` file to change the port, h // filepath config/hyde.php 'server' => [ + // The default port the preview is served on 'port' => env('SERVER_PORT', 8080), + + // The default host the preview is served on 'host' => env('SERVER_HOST', 'localhost'), + + // Should preview pages be saved to the output directory? 'save_preview' => env('SERVER_SAVE_PREVIEW', false), + + // Should the live edit feature be enabled? + 'live_edit' => env('SERVER_LIVE_EDIT', true), + + // Configure the realtime compiler dashboard + 'dashboard' => [ + // Should the realtime compiler dashboard be enabled? + 'enabled' => env('SERVER_DASHBOARD', true), + + // Can the dashboard make edits to the project file system? + 'interactive' => true, + + // Should the dashboard show tips? + 'tips' => true, + ], + ], ``` From 851421d0ebb96171351a7e1caeb5d67049aad3d5 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 09:51:05 +0100 Subject: [PATCH 06/47] Method to print the Vite message --- packages/realtime-compiler/src/ConsoleOutput.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/realtime-compiler/src/ConsoleOutput.php b/packages/realtime-compiler/src/ConsoleOutput.php index a96cd0329be..0d3992c27b7 100644 --- a/packages/realtime-compiler/src/ConsoleOutput.php +++ b/packages/realtime-compiler/src/ConsoleOutput.php @@ -54,6 +54,16 @@ public function printStartMessage(string $host, int $port, array $environment = render("
$body
"); } + public function printViteMessage(): void + { + $this->output->writeln([ + '', + ' Starting Vite development server...', + ' HMR will be available on port 3000', + '', + ]); + } + public function getFormatter(): Closure { return function (string $type, string $line): void { From d2afceaff64419102ce2a9cc6266fde9e74de675 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 09:54:53 +0100 Subject: [PATCH 07/47] Run processes in parallel --- .../src/Console/Commands/ServeCommand.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index 2c73e17cd0c..fb59eb1b1df 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -71,7 +71,10 @@ protected function runViteProcess(): void { $viteCommand = 'npm run dev'; - Process::forever()->env($this->getViteEnvironmentVariables())->run($viteCommand, $this->getViteOutputHandler()); + Process::forever() + ->start($viteCommand, function (string $type, string $line): void { + $this->output->write($line); + }); $this->info('Vite development server started for HMR.'); } @@ -108,7 +111,14 @@ protected function getExecutablePath(): string protected function runServerProcess(string $command): void { - Process::forever()->env($this->getEnvironmentVariables())->run($command, $this->getOutputHandler()); + Process::forever() + ->env($this->getEnvironmentVariables()) + ->start($command, $this->getOutputHandler()); + + // Keep the main process running + while (true) { + usleep(1000000); // Sleep for 1 second + } } protected function getEnvironmentVariables(): array From 6ad2b0446d8d56b0219c90bbe100c37138f0a0bb Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 09:58:55 +0100 Subject: [PATCH 08/47] Stream both process outputs --- .../src/Console/Commands/ServeCommand.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index fb59eb1b1df..b4a286a7ea9 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -72,9 +72,8 @@ protected function runViteProcess(): void $viteCommand = 'npm run dev'; Process::forever() - ->start($viteCommand, function (string $type, string $line): void { - $this->output->write($line); - }); + ->env($this->getViteEnvironmentVariables()) + ->start($viteCommand, $this->getViteOutputHandler()); $this->info('Vite development server started for HMR.'); } @@ -111,10 +110,19 @@ protected function getExecutablePath(): string protected function runServerProcess(string $command): void { - Process::forever() + $process = Process::forever() ->env($this->getEnvironmentVariables()) ->start($command, $this->getOutputHandler()); + if (PHP_OS_FAMILY !== 'Windows') { + pcntl_async_signals(true); + pcntl_signal(SIGINT, function () use ($process) { + $this->info("\nShutting down servers..."); + $process->stop(); + exit; + }); + } + // Keep the main process running while (true) { usleep(1000000); // Sleep for 1 second From b6361aba657492c0217d6240122783b987e794c4 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 10:10:37 +0100 Subject: [PATCH 09/47] Create vite-index-page.html --- .../resources/vite-index-page.html | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 packages/realtime-compiler/resources/vite-index-page.html diff --git a/packages/realtime-compiler/resources/vite-index-page.html b/packages/realtime-compiler/resources/vite-index-page.html new file mode 100644 index 00000000000..5c2a24e33b1 --- /dev/null +++ b/packages/realtime-compiler/resources/vite-index-page.html @@ -0,0 +1,66 @@ + + + + + + + HydePHP Vite + + + + + + +
+
+
+ +
+
+

This is the Vite development server that provides Hot Module Replacement for your HydePHP site.

+

Your HydePHP site is running on the Realtime Compiler server.

+

HydePHP Realtime Compiler

+

The main development server for your HydePHP site.

+

HydePHP Dashboard

+

Access the realtime compiler dashboard to monitor your site.

+
+
+

+ Your HydePHP site is running at:
+ http://localhost:8080 +

+

Want to learn more about HydePHP?

+

+ Read the docs → +

+
+
+
+
+
+ + \ No newline at end of file From c87203ac140c9a9254831eed4c16dc836b120006 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 10:10:50 +0100 Subject: [PATCH 10/47] Render Vite index page --- vite.config.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/vite.config.js b/vite.config.js index ff20640ebe1..ee596703bba 100644 --- a/vite.config.js +++ b/vite.config.js @@ -6,6 +6,8 @@ import { defineConfig } from 'vite'; import { resolve } from 'path'; import tailwindcss from 'tailwindcss'; import autoprefixer from 'autoprefixer'; +import fs from 'fs'; +import path from 'path'; export default defineConfig({ server: { @@ -14,7 +16,25 @@ export default defineConfig({ host: 'localhost', port: 3000, }, + middlewareMode: false, }, + plugins: [ + { + name: 'hyde-vite-server', + configureServer(server) { + server.middlewares.use((req, res, next) => { + if (req.url === '/') { + res.end(fs.readFileSync( + path.resolve(__dirname, 'vendor/hyde/realtime-compiler/resources/vite-index-page.html'), + 'utf-8' + )); + } else { + next(); + } + }); + }, + }, + ], css: { postcss: { plugins: [ From ed2404d29cfdd97fdaec9251c1e83ed8e7b3d0be Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 10:26:24 +0100 Subject: [PATCH 11/47] Draft facade methods --- packages/framework/src/Facades/Vite.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/framework/src/Facades/Vite.php b/packages/framework/src/Facades/Vite.php index d387e0e58a0..dd2a461fbdd 100644 --- a/packages/framework/src/Facades/Vite.php +++ b/packages/framework/src/Facades/Vite.php @@ -7,5 +7,17 @@ */ class Vite { - // + public static function running(): bool + { + // TODO: Implement this + + return true; + } + + public static function assets(array $paths): string + { + // TODO: Implement this + + return ''; + } } From f5d6c84becce6186e9484b6e79cef08e219dc9b6 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 10:30:53 +0100 Subject: [PATCH 12/47] Alias Vite facade --- app/config.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/config.php b/app/config.php index aed2519bbce..26156665112 100644 --- a/app/config.php +++ b/app/config.php @@ -92,6 +92,7 @@ 'Hyde' => Hyde\Hyde::class, 'Site' => \Hyde\Facades\Site::class, 'Meta' => \Hyde\Facades\Meta::class, + 'Vite' => \Hyde\Facades\Vite::class, 'Asset' => \Hyde\Facades\Asset::class, 'Author' => \Hyde\Facades\Author::class, 'HydeFront' => \Hyde\Facades\HydeFront::class, From e59cecf118641a10c21fb43a0414cdfa0a35aed8 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 10:32:02 +0100 Subject: [PATCH 13/47] Add strict types declaration --- packages/framework/src/Facades/Vite.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/framework/src/Facades/Vite.php b/packages/framework/src/Facades/Vite.php index dd2a461fbdd..ccb2d2f40ba 100644 --- a/packages/framework/src/Facades/Vite.php +++ b/packages/framework/src/Facades/Vite.php @@ -1,5 +1,7 @@ Date: Tue, 12 Nov 2024 10:41:57 +0100 Subject: [PATCH 14/47] Implement the Vite asset method --- packages/framework/src/Facades/Vite.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/framework/src/Facades/Vite.php b/packages/framework/src/Facades/Vite.php index ccb2d2f40ba..0de70e06355 100644 --- a/packages/framework/src/Facades/Vite.php +++ b/packages/framework/src/Facades/Vite.php @@ -4,6 +4,8 @@ namespace Hyde\Facades; +use Illuminate\Support\HtmlString; + /** * Vite facade for handling Vite-related operations. */ @@ -16,10 +18,20 @@ public static function running(): bool return true; } - public static function assets(array $paths): string + public static function assets(array $paths): HtmlString { - // TODO: Implement this + $html = sprintf(''); + + foreach ($paths as $path) { + if (str_ends_with($path, '.css')) { + $html .= sprintf('', $path); + } + + if (str_ends_with($path, '.js')) { + $html .= sprintf('', $path); + } + } - return ''; + return new HtmlString($html); } } From 2683f861872a2792c2736087df0f9005024f59e4 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 10:42:13 +0100 Subject: [PATCH 15/47] Serve Vite assets when running --- .../resources/views/layouts/styles.blade.php | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/framework/resources/views/layouts/styles.blade.php b/packages/framework/resources/views/layouts/styles.blade.php index 8bf4d5bcc55..cf374c04c99 100644 --- a/packages/framework/resources/views/layouts/styles.blade.php +++ b/packages/framework/resources/views/layouts/styles.blade.php @@ -2,17 +2,22 @@ {{-- The compiled Tailwind/App styles --}} -@if(config('hyde.load_app_styles_from_cdn', false)) - -@elseif(Asset::exists('app.css')) - -@endif +@if(Vite::running()) + {{ Vite::assets(['resources/assets/app.css']) }} +@else + @if(config('hyde.load_app_styles_from_cdn', false)) + + @elseif(Asset::exists('app.css')) + + @endif + -{{-- Dynamic TailwindCSS Play CDN --}} -@if(config('hyde.use_play_cdn', false)) - - - + {{-- Dynamic TailwindCSS Play CDN --}} + @if(config('hyde.use_play_cdn', false)) + + + + @endif @endif {{-- Add any extra styles to include after the others --}} From 58a7f7a58cfa986a921a1d956eec8998d351c8e8 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 10:50:30 +0100 Subject: [PATCH 16/47] Implement the Vite connection checker --- packages/framework/src/Facades/Vite.php | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/framework/src/Facades/Vite.php b/packages/framework/src/Facades/Vite.php index 0de70e06355..8c6174062fd 100644 --- a/packages/framework/src/Facades/Vite.php +++ b/packages/framework/src/Facades/Vite.php @@ -11,11 +11,29 @@ */ class Vite { + protected static bool $isRunning = false; + protected static bool $hasChecked = false; + public static function running(): bool { - // TODO: Implement this + if (! static::$hasChecked) { + static::checkViteStatus(); + } + + return static::$isRunning; + } - return true; + protected static function checkViteStatus(): void + { + static::$hasChecked = true; + + // Try to establish a connection to the Vite server + $connection = @fsockopen('localhost', 3000, $errno, $errstr, 0.1); + + if ($connection) { + static::$isRunning = true; + fclose($connection); + } } public static function assets(array $paths): HtmlString From 86e0180eb8f72e14dce53a81ea826c97ce6e03bd Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 10:55:28 +0100 Subject: [PATCH 17/47] Use much faster environment variable for Vite status The socket check takes a few milliseconds on Mac which is totally okay, but about 500ms on Windows which is not acceptable for each page visit. --- .../src/Console/Commands/ServeCommand.php | 1 + packages/framework/src/Facades/Vite.php | 22 +------------------ 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index b4a286a7ea9..69488e9f45d 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -137,6 +137,7 @@ protected function getEnvironmentVariables(): array 'HYDE_SERVER_DASHBOARD' => $this->parseEnvironmentOption('dashboard'), 'HYDE_PRETTY_URLS' => $this->parseEnvironmentOption('pretty-urls'), 'HYDE_PLAY_CDN' => $this->parseEnvironmentOption('play-cdn'), + 'HYDE_SERVER_VITE' => $this->option('vite') ? 'enabled' : null, ]); } diff --git a/packages/framework/src/Facades/Vite.php b/packages/framework/src/Facades/Vite.php index 8c6174062fd..87dc1841263 100644 --- a/packages/framework/src/Facades/Vite.php +++ b/packages/framework/src/Facades/Vite.php @@ -11,29 +11,9 @@ */ class Vite { - protected static bool $isRunning = false; - protected static bool $hasChecked = false; - public static function running(): bool { - if (! static::$hasChecked) { - static::checkViteStatus(); - } - - return static::$isRunning; - } - - protected static function checkViteStatus(): void - { - static::$hasChecked = true; - - // Try to establish a connection to the Vite server - $connection = @fsockopen('localhost', 3000, $errno, $errstr, 0.1); - - if ($connection) { - static::$isRunning = true; - fclose($connection); - } + return env('HYDE_SERVER_VITE') === 'enabled'; } public static function assets(array $paths): HtmlString From b0833006004ac867c317163927b34de514b10fcc Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 10:58:19 +0100 Subject: [PATCH 18/47] Flush output --- .../src/Console/Commands/ServeCommand.php | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index 69488e9f45d..1ba7c13ca3a 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -73,7 +73,17 @@ protected function runViteProcess(): void Process::forever() ->env($this->getViteEnvironmentVariables()) - ->start($viteCommand, $this->getViteOutputHandler()); + ->tty(true) + ->start($viteCommand, function (string $type, string $output): void { + if (Process::ERR === $type) { + $this->error($output); + } else { + $this->line($output); + } + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + flush(); + } + }); $this->info('Vite development server started for HMR.'); } @@ -88,8 +98,15 @@ protected function getViteEnvironmentVariables(): array protected function getViteOutputHandler(): Closure { - return function (string $type, string $line): void { - $this->output->write($line); + return function (string $type, string $output): void { + if (Process::ERR === $type) { + $this->error($output); + } else { + $this->line($output); + } + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + flush(); + } }; } @@ -112,7 +129,14 @@ protected function runServerProcess(string $command): void { $process = Process::forever() ->env($this->getEnvironmentVariables()) - ->start($command, $this->getOutputHandler()); + ->tty(true) + ->start($command, function (string $type, string $output): void { + if (Process::ERR === $type) { + $this->error($output); + } else { + $this->line($output); + } + }); if (PHP_OS_FAMILY !== 'Windows') { pcntl_async_signals(true); @@ -123,9 +147,11 @@ protected function runServerProcess(string $command): void }); } - // Keep the main process running while (true) { usleep(1000000); // Sleep for 1 second + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + flush(); + } } } From da82fa60bc6a1310b7093e9d4ee9376827631740 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 11:02:28 +0100 Subject: [PATCH 19/47] Start over with Vite server --- .../src/Console/Commands/ServeCommand.php | 74 +------------------ 1 file changed, 1 insertion(+), 73 deletions(-) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index 1ba7c13ca3a..84f28f49e92 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -67,49 +67,6 @@ public function safeHandle(): int return Command::SUCCESS; } - protected function runViteProcess(): void - { - $viteCommand = 'npm run dev'; - - Process::forever() - ->env($this->getViteEnvironmentVariables()) - ->tty(true) - ->start($viteCommand, function (string $type, string $output): void { - if (Process::ERR === $type) { - $this->error($output); - } else { - $this->line($output); - } - if (defined('PHP_WINDOWS_VERSION_BUILD')) { - flush(); - } - }); - - $this->info('Vite development server started for HMR.'); - } - - protected function getViteEnvironmentVariables(): array - { - return Arr::whereNotNull([ - 'NODE_ENV' => 'development', - 'PORT' => 3000, - ]); - } - - protected function getViteOutputHandler(): Closure - { - return function (string $type, string $output): void { - if (Process::ERR === $type) { - $this->error($output); - } else { - $this->line($output); - } - if (defined('PHP_WINDOWS_VERSION_BUILD')) { - flush(); - } - }; - } - protected function getHostSelection(): string { return (string) $this->option('host') ?: Config::getString('hyde.server.host', 'localhost'); @@ -127,32 +84,7 @@ protected function getExecutablePath(): string protected function runServerProcess(string $command): void { - $process = Process::forever() - ->env($this->getEnvironmentVariables()) - ->tty(true) - ->start($command, function (string $type, string $output): void { - if (Process::ERR === $type) { - $this->error($output); - } else { - $this->line($output); - } - }); - - if (PHP_OS_FAMILY !== 'Windows') { - pcntl_async_signals(true); - pcntl_signal(SIGINT, function () use ($process) { - $this->info("\nShutting down servers..."); - $process->stop(); - exit; - }); - } - - while (true) { - usleep(1000000); // Sleep for 1 second - if (defined('PHP_WINDOWS_VERSION_BUILD')) { - flush(); - } - } + Process::forever()->env($this->getEnvironmentVariables())->run($command, $this->getOutputHandler()); } protected function getEnvironmentVariables(): array @@ -179,10 +111,6 @@ protected function printStartMessage(): void $this->useBasicOutput() ? $this->output->writeln('Starting the HydeRC server... Press Ctrl+C to stop') : $this->console->printStartMessage($this->getHostSelection(), $this->getPortSelection(), $this->getEnvironmentVariables()); - - if ($this->option('vite')) { - $this->console->printViteMessage(); - } } protected function getOutputHandler(): Closure From 310407ebc0efb0baef53a886b8f61f0e676b6fe6 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 11:09:34 +0100 Subject: [PATCH 20/47] Simpler Vite process handler --- packages/framework/src/Console/Commands/ServeCommand.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index 84f28f49e92..76da51c2c95 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -177,4 +177,11 @@ protected function getOpenCommand(string $osFamily): ?string default => null }; } + + protected function runViteProcess(): void + { + Process::forever()->start('npm run dev', function (string $type, string $output): void { + $this->output->write($output); + }); + } } From d0fc208cad18aa97f9d636b3d53873ad8d27ec1e Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 11:23:36 +0100 Subject: [PATCH 21/47] Run server asynchronously --- packages/framework/src/Console/Commands/ServeCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index 76da51c2c95..456a4bd7ceb 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -84,7 +84,7 @@ protected function getExecutablePath(): string protected function runServerProcess(string $command): void { - Process::forever()->env($this->getEnvironmentVariables())->run($command, $this->getOutputHandler()); + $this->server = Process::forever()->env($this->getEnvironmentVariables())->start($command, $this->getOutputHandler()); } protected function getEnvironmentVariables(): array From b6d96c47f68e58170a7d4f640683007754e99836 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 11:23:51 +0100 Subject: [PATCH 22/47] Sleep while server is running --- packages/framework/src/Console/Commands/ServeCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index 456a4bd7ceb..f9e233fcf98 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -64,6 +64,10 @@ public function safeHandle(): int $this->runServerProcess($command); + while ($this->server->running()) { + sleep(1); + } + return Command::SUCCESS; } From 6d40de62b895427b0f8eadefac66808450cbd27b Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 11:24:07 +0100 Subject: [PATCH 23/47] Handle output from sleep loop --- packages/framework/src/Console/Commands/ServeCommand.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index f9e233fcf98..9163f28b9e7 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -65,6 +65,10 @@ public function safeHandle(): int $this->runServerProcess($command); while ($this->server->running()) { + if (isset($this->vite) && $this->vite->running()) { + echo $this->vite->latestOutput(); + } + sleep(1); } @@ -184,8 +188,6 @@ protected function getOpenCommand(string $osFamily): ?string protected function runViteProcess(): void { - Process::forever()->start('npm run dev', function (string $type, string $output): void { - $this->output->write($output); - }); + $this->vite = Process::forever()->start('npm run dev'); } } From 6ea538f8fef0ed27d59b976e89c64cf993d4afe3 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 11:24:13 +0100 Subject: [PATCH 24/47] Declare properties --- packages/framework/src/Console/Commands/ServeCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index 9163f28b9e7..8d67f7bb2be 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -7,6 +7,7 @@ use Closure; use Hyde\Hyde; use Hyde\Facades\Config; +use Illuminate\Process\InvokedProcess; use Illuminate\Support\Arr; use InvalidArgumentException; use Hyde\Console\Concerns\Command; @@ -43,6 +44,9 @@ class ServeCommand extends Command protected ConsoleOutput $console; + protected InvokedProcess $server; + protected InvokedProcess $vite; + public function safeHandle(): int { $this->configureOutput(); From 6a19d1f31bc5e9a05d314ef2128150860c6c6a45 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 12:05:35 +0100 Subject: [PATCH 25/47] Type against the contract --- packages/framework/src/Console/Commands/ServeCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index 8d67f7bb2be..11ce7a5d59d 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -7,7 +7,7 @@ use Closure; use Hyde\Hyde; use Hyde\Facades\Config; -use Illuminate\Process\InvokedProcess; +use Illuminate\Contracts\Process\InvokedProcess; use Illuminate\Support\Arr; use InvalidArgumentException; use Hyde\Console\Concerns\Command; From f1703660c205cb60bf72c8520463ccb0e1287729 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 11:34:21 +0100 Subject: [PATCH 26/47] Sleep for 100ms instead of 1 second --- packages/framework/src/Console/Commands/ServeCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index 11ce7a5d59d..eca39f70ac5 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -73,7 +73,7 @@ public function safeHandle(): int echo $this->vite->latestOutput(); } - sleep(1); + usleep(100000); // Sleep for 100ms } return Command::SUCCESS; From d9355112444b924e9cd44f29d75ee149f8148b7c Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 11:36:56 +0100 Subject: [PATCH 27/47] Remove unused Vite start message helper --- packages/realtime-compiler/src/ConsoleOutput.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/realtime-compiler/src/ConsoleOutput.php b/packages/realtime-compiler/src/ConsoleOutput.php index 0d3992c27b7..a96cd0329be 100644 --- a/packages/realtime-compiler/src/ConsoleOutput.php +++ b/packages/realtime-compiler/src/ConsoleOutput.php @@ -54,16 +54,6 @@ public function printStartMessage(string $host, int $port, array $environment = render("
$body
"); } - public function printViteMessage(): void - { - $this->output->writeln([ - '', - ' Starting Vite development server...', - ' HMR will be available on port 3000', - '', - ]); - } - public function getFormatter(): Closure { return function (string $type, string $line): void { From 406e8ae4e471a674e060a7f747da19dd88cf2b13 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 11:42:16 +0100 Subject: [PATCH 28/47] Add Vite information to output start message --- packages/realtime-compiler/src/ConsoleOutput.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/realtime-compiler/src/ConsoleOutput.php b/packages/realtime-compiler/src/ConsoleOutput.php index a96cd0329be..93f1d44cbe5 100644 --- a/packages/realtime-compiler/src/ConsoleOutput.php +++ b/packages/realtime-compiler/src/ConsoleOutput.php @@ -35,6 +35,8 @@ public function printStartMessage(string $host, int $port, array $environment = sprintf('Listening on: %s', $url, $url), (config('hyde.server.dashboard.enabled') || Arr::has($environment, 'HYDE_SERVER_DASHBOARD')) && Arr::get($environment, 'HYDE_SERVER_DASHBOARD') === 'enabled' ? sprintf('Live dashboard: %s/dashboard', $url, $url) : null, + Arr::get($environment, 'HYDE_SERVER_VITE') === 'enabled' ? + sprintf('Vite HMR server: http://%s:3000', $host, $host) : null, '', ]); From 8419a685516f3d67b3cde9314c7a88d21c426a39 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 11:55:22 +0100 Subject: [PATCH 29/47] Support standalone Vite servers --- packages/framework/src/Facades/Vite.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/framework/src/Facades/Vite.php b/packages/framework/src/Facades/Vite.php index 87dc1841263..e016a35bc1c 100644 --- a/packages/framework/src/Facades/Vite.php +++ b/packages/framework/src/Facades/Vite.php @@ -13,7 +13,22 @@ class Vite { public static function running(): bool { - return env('HYDE_SERVER_VITE') === 'enabled'; + // Check if Vite was enabled via the serve command + if (env('HYDE_SERVER_VITE') === 'enabled') { + return true; + } + + // Check if Vite dev server is running by attempting to connect to it + // Todo: Check performance on Windows (takes less than 1ms on macOS) + $server = @fsockopen('localhost', 3000, $errno, $errstr, 0.1); + + if ($server) { + fclose($server); + + return true; + } + + return false; } public static function assets(array $paths): HtmlString From 57a22d9cf1c570a664469b18ef8b15db8e095349 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 11:56:27 +0100 Subject: [PATCH 30/47] Use default Vite port --- packages/framework/src/Facades/Vite.php | 8 ++++---- packages/realtime-compiler/resources/dashboard.blade.php | 2 +- packages/realtime-compiler/src/ConsoleOutput.php | 2 +- vite.config.js | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/framework/src/Facades/Vite.php b/packages/framework/src/Facades/Vite.php index e016a35bc1c..a8dd219ebdf 100644 --- a/packages/framework/src/Facades/Vite.php +++ b/packages/framework/src/Facades/Vite.php @@ -20,7 +20,7 @@ public static function running(): bool // Check if Vite dev server is running by attempting to connect to it // Todo: Check performance on Windows (takes less than 1ms on macOS) - $server = @fsockopen('localhost', 3000, $errno, $errstr, 0.1); + $server = @fsockopen('localhost', 5173, $errno, $errstr, 0.1); if ($server) { fclose($server); @@ -33,15 +33,15 @@ public static function running(): bool public static function assets(array $paths): HtmlString { - $html = sprintf(''); + $html = sprintf(''); foreach ($paths as $path) { if (str_ends_with($path, '.css')) { - $html .= sprintf('', $path); + $html .= sprintf('', $path); } if (str_ends_with($path, '.js')) { - $html .= sprintf('', $path); + $html .= sprintf('', $path); } } diff --git a/packages/realtime-compiler/resources/dashboard.blade.php b/packages/realtime-compiler/resources/dashboard.blade.php index d7bcf0fb77a..ba7301e8d83 100644 --- a/packages/realtime-compiler/resources/dashboard.blade.php +++ b/packages/realtime-compiler/resources/dashboard.blade.php @@ -98,7 +98,7 @@ await navigator.clipboard.writeText(data); document.getElementById("copyPathToClipboardButtonIcon").style.display = 'none'; document.getElementById("copyPathToClipboardButtonIconSuccess").style.display = 'inline'; - await new Promise(resolve => setTimeout(resolve, 3000)).then(function () { + await new Promise(resolve => setTimeout(resolve, 5173)).then(function () { document.getElementById("copyPathToClipboardButtonIcon").style.display = 'inline'; document.getElementById("copyPathToClipboardButtonIconSuccess").style.display = 'none'; }); diff --git a/packages/realtime-compiler/src/ConsoleOutput.php b/packages/realtime-compiler/src/ConsoleOutput.php index 93f1d44cbe5..2c9b216d72d 100644 --- a/packages/realtime-compiler/src/ConsoleOutput.php +++ b/packages/realtime-compiler/src/ConsoleOutput.php @@ -36,7 +36,7 @@ public function printStartMessage(string $host, int $port, array $environment = (config('hyde.server.dashboard.enabled') || Arr::has($environment, 'HYDE_SERVER_DASHBOARD')) && Arr::get($environment, 'HYDE_SERVER_DASHBOARD') === 'enabled' ? sprintf('Live dashboard: %s/dashboard', $url, $url) : null, Arr::get($environment, 'HYDE_SERVER_VITE') === 'enabled' ? - sprintf('Vite HMR server: http://%s:3000', $host, $host) : null, + sprintf('Vite HMR server: http://%s:5173', $host, $host) : null, '', ]); diff --git a/vite.config.js b/vite.config.js index ee596703bba..17a2bcca8be 100644 --- a/vite.config.js +++ b/vite.config.js @@ -11,10 +11,10 @@ import path from 'path'; export default defineConfig({ server: { - port: 3000, + port: 5173, hmr: { host: 'localhost', - port: 3000, + port: 5173, }, middlewareMode: false, }, From f1c421e3f6b1fff880d23b50b3e1c96f6b6d9d59 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 13:44:49 +0100 Subject: [PATCH 31/47] Add widths for layout to work without network --- packages/realtime-compiler/resources/vite-index-page.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/realtime-compiler/resources/vite-index-page.html b/packages/realtime-compiler/resources/vite-index-page.html index 5c2a24e33b1..b6902af7381 100644 --- a/packages/realtime-compiler/resources/vite-index-page.html +++ b/packages/realtime-compiler/resources/vite-index-page.html @@ -16,13 +16,13 @@
- + - + - + From c0d67ffdf03c7b6d90410acee64c159d1c34f3de Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 13:13:10 +0100 Subject: [PATCH 32/47] Update tests for asynchronous server process --- .../tests/Feature/Commands/ServeCommandTest.php | 10 ++++++++-- .../tests/Unit/ServeCommandOptionsUnitTest.php | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/framework/tests/Feature/Commands/ServeCommandTest.php b/packages/framework/tests/Feature/Commands/ServeCommandTest.php index c8569cc38ae..ebabf130cd0 100644 --- a/packages/framework/tests/Feature/Commands/ServeCommandTest.php +++ b/packages/framework/tests/Feature/Commands/ServeCommandTest.php @@ -7,6 +7,7 @@ use Closure; use Hyde\Hyde; use Hyde\Testing\TestCase; +use Illuminate\Contracts\Process\InvokedProcess; use Illuminate\Support\Facades\Process; use TypeError; @@ -138,6 +139,11 @@ public function testHydeServeCommandWithInvalidConfigValue() public function testHydeServeCommandPassesThroughProcessOutput() { + $mockProcess = mock(InvokedProcess::class); + $mockProcess->shouldReceive('running') + ->once() + ->andReturn(false); + Process::shouldReceive('forever') ->once() ->withNoArgs() @@ -148,14 +154,14 @@ public function testHydeServeCommandPassesThroughProcessOutput() ->with(['HYDE_SERVER_REQUEST_OUTPUT' => false]) ->andReturnSelf(); - Process::shouldReceive('run') + Process::shouldReceive('start') ->once() ->withArgs(function (string $command, Closure $handle) { $handle('type', 'foo'); return $command === "php -S localhost:8080 {$this->binaryPath()}"; }) - ->andReturnSelf(); + ->andReturn($mockProcess); $this->artisan('serve --no-ansi') ->expectsOutput('Starting the HydeRC server... Press Ctrl+C to stop') diff --git a/packages/framework/tests/Unit/ServeCommandOptionsUnitTest.php b/packages/framework/tests/Unit/ServeCommandOptionsUnitTest.php index f76d70e35ee..2d11dcc7d91 100644 --- a/packages/framework/tests/Unit/ServeCommandOptionsUnitTest.php +++ b/packages/framework/tests/Unit/ServeCommandOptionsUnitTest.php @@ -367,6 +367,8 @@ protected function printStartMessage(): void protected function runServerProcess(string $command): void { + $this->server = Mockery::mock(\Illuminate\Contracts\Process\InvokedProcess::class); + $this->server->shouldReceive('running')->once()->andReturn(false); } protected function openInBrowser(string $path = '/'): void From 704f67cde5b9bcd470a3d9507ae175c955e12ce2 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 14:16:59 +0100 Subject: [PATCH 33/47] Use the sleep facade --- packages/framework/src/Console/Commands/ServeCommand.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index eca39f70ac5..ea74db68609 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -9,6 +9,7 @@ use Hyde\Facades\Config; use Illuminate\Contracts\Process\InvokedProcess; use Illuminate\Support\Arr; +use Illuminate\Support\Sleep; use InvalidArgumentException; use Hyde\Console\Concerns\Command; use Hyde\RealtimeCompiler\ConsoleOutput; @@ -73,7 +74,7 @@ public function safeHandle(): int echo $this->vite->latestOutput(); } - usleep(100000); // Sleep for 100ms + Sleep::usleep(100000); // 100ms } return Command::SUCCESS; From b03ec7fa31e2a0136f92cacf63f6fbf63fa9903f Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 13:52:24 +0100 Subject: [PATCH 34/47] Add tests to cover introduced code --- .../src/Console/Commands/ServeCommand.php | 2 +- .../Feature/Commands/ServeCommandTest.php | 85 +++++++++++++++++++ .../tests/Unit/Facades/ViteFacadeTest.php | 67 ++++++++++++++- .../Unit/ServeCommandOptionsUnitTest.php | 56 ++++++++++++ 4 files changed, 208 insertions(+), 2 deletions(-) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index ea74db68609..00a9628cce3 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -71,7 +71,7 @@ public function safeHandle(): int while ($this->server->running()) { if (isset($this->vite) && $this->vite->running()) { - echo $this->vite->latestOutput(); + $this->line($this->vite->latestOutput()); } Sleep::usleep(100000); // 100ms diff --git a/packages/framework/tests/Feature/Commands/ServeCommandTest.php b/packages/framework/tests/Feature/Commands/ServeCommandTest.php index ebabf130cd0..d12916858a0 100644 --- a/packages/framework/tests/Feature/Commands/ServeCommandTest.php +++ b/packages/framework/tests/Feature/Commands/ServeCommandTest.php @@ -180,6 +180,91 @@ public function testWithFancyOutput() Process::assertRan("php -S localhost:8080 {$this->binaryPath()}"); } + public function testHydeServeCommandWithViteOption() + { + $mockViteProcess = mock(InvokedProcess::class); + $mockViteProcess->shouldReceive('running') + ->once() + ->andReturn(true); + $mockViteProcess->shouldReceive('latestOutput') + ->once() + ->andReturn('vite latest output'); + + $mockServerProcess = mock(InvokedProcess::class); + $mockServerProcess->shouldReceive('running') + ->times(2) + ->andReturn(true, false); + + Process::shouldReceive('forever') + ->twice() + ->withNoArgs() + ->andReturnSelf(); + + Process::shouldReceive('env') + ->once() + ->with(['HYDE_SERVER_REQUEST_OUTPUT' => false, 'HYDE_SERVER_VITE' => 'enabled']) + ->andReturnSelf(); + + Process::shouldReceive('start') + ->once() + ->with('npm run dev') + ->andReturn($mockViteProcess); + + Process::shouldReceive('start') + ->once() + ->withArgs(function (string $command, Closure $output) { + $output('stdout', 'server output'); + + return $command === "php -S localhost:8080 {$this->binaryPath()}"; + }) + ->andReturn($mockServerProcess); + + $this->artisan('serve --no-ansi --vite') + ->expectsOutput('Starting the HydeRC server... Press Ctrl+C to stop') + ->expectsOutput('server output') + ->expectsOutput('vite latest output') + ->assertExitCode(0); + } + + public function testHydeServeCommandWithViteOptionButViteNotRunning() + { + $mockViteProcess = mock(InvokedProcess::class); + $mockViteProcess->shouldReceive('running') + ->once() + ->andReturn(false); + + $mockServerProcess = mock(InvokedProcess::class); + $mockServerProcess->shouldReceive('running') + ->times(2) + ->andReturn(true, false); + + Process::shouldReceive('forever') + ->twice() + ->withNoArgs() + ->andReturnSelf(); + + Process::shouldReceive('env') + ->once() + ->with(['HYDE_SERVER_REQUEST_OUTPUT' => false, 'HYDE_SERVER_VITE' => 'enabled']) + ->andReturnSelf(); + + Process::shouldReceive('start') + ->once() + ->with('npm run dev') + ->andReturn($mockViteProcess); + + Process::shouldReceive('start') + ->once() + ->withArgs(function (string $command, Closure $handle) { + return $command === "php -S localhost:8080 {$this->binaryPath()}"; + }) + ->andReturn($mockServerProcess); + + $this->artisan('serve --no-ansi --vite') + ->expectsOutput('Starting the HydeRC server... Press Ctrl+C to stop') + ->assertExitCode(0); + } + protected function binaryPath(): string { return Hyde::path('vendor/hyde/realtime-compiler/bin/server.php'); diff --git a/packages/framework/tests/Unit/Facades/ViteFacadeTest.php b/packages/framework/tests/Unit/Facades/ViteFacadeTest.php index 5ad403733b8..54d20d115fb 100644 --- a/packages/framework/tests/Unit/Facades/ViteFacadeTest.php +++ b/packages/framework/tests/Unit/Facades/ViteFacadeTest.php @@ -3,11 +3,76 @@ namespace Hyde\Framework\Testing\Unit\Facades; use Hyde\Testing\UnitTestCase; +use Hyde\Facades\Vite; /** * @covers \Hyde\Facades\Vite */ class ViteFacadeTest extends UnitTestCase { - // + public function testRunningReturnsTrueWhenEnvironmentVariableIsSet() + { + putenv('HYDE_SERVER_VITE=enabled'); + + $this->assertTrue(Vite::running()); + + putenv('HYDE_SERVER_VITE'); + } + + public function testRunningReturnsTrueWhenViteServerIsAccessible() + { + // Create a mock server to simulate Vite + $server = stream_socket_server('tcp://localhost:5173'); + + $this->assertTrue(Vite::running()); + + // Clean up + stream_socket_shutdown($server, STREAM_SHUT_RDWR); + } + + public function testRunningReturnsFalseWhenViteServerIsNotAccessible() + { + $this->assertFalse(Vite::running()); + } + + public function testAssetsMethodGeneratesCorrectHtmlForJavaScriptFiles() + { + $html = Vite::assets(['resources/js/app.js']); + + $expected = '' + .''; + + $this->assertSame($expected, (string) $html); + } + + public function testAssetsMethodGeneratesCorrectHtmlForCssFiles() + { + $html = Vite::assets(['resources/css/app.css']); + + $expected = '' + .''; + + $this->assertSame($expected, (string) $html); + } + + public function testAssetsMethodGeneratesCorrectHtmlForMultipleFiles() + { + $html = Vite::assets([ + 'resources/js/app.js', + 'resources/css/app.css', + 'resources/js/other.js', + ]); + + $expected = '' + .'' + .'' + .''; + + $this->assertSame($expected, (string) $html); + } + + public function testAssetsMethodReturnsHtmlString() + { + $this->assertInstanceOf(\Illuminate\Support\HtmlString::class, Vite::assets([])); + } } diff --git a/packages/framework/tests/Unit/ServeCommandOptionsUnitTest.php b/packages/framework/tests/Unit/ServeCommandOptionsUnitTest.php index 2d11dcc7d91..cf26d2a842e 100644 --- a/packages/framework/tests/Unit/ServeCommandOptionsUnitTest.php +++ b/packages/framework/tests/Unit/ServeCommandOptionsUnitTest.php @@ -335,6 +335,29 @@ public function testGetOpenCommandForUnknownOS() $this->assertNull($this->getMock()->getOpenCommand('UnknownOS')); } + public function testViteOptionPropagatesToEnvironmentVariables() + { + $command = $this->getMock(['vite' => true]); + $this->assertSame('enabled', $command->getEnvironmentVariables()['HYDE_SERVER_VITE']); + + $command = $this->getMock(['vite' => false]); + $this->assertFalse(isset($command->getEnvironmentVariables()['HYDE_SERVER_VITE'])); + + $command = $this->getMock(); + $this->assertFalse(isset($command->getEnvironmentVariables()['HYDE_SERVER_VITE'])); + } + + public function testWithViteArgument() + { + HydeKernel::setInstance(new HydeKernel()); + + $command = $this->getViteServeCommandMock(['vite' => true]); + + $command->safeHandle(); + + $this->assertTrue($command->viteProcessStarted); + } + protected function getTestRunnerBinary(): string { return match (PHP_OS_FAMILY) { @@ -378,6 +401,39 @@ protected function openInBrowser(string $path = '/'): void } }; } + + protected function getViteServeCommandMock(array $arguments): ServeCommandMock + { + return new class($arguments) extends ServeCommandMock + { + public bool $viteProcessStarted = false; + + // Void unrelated methods + protected function configureOutput(): void + { + } + + protected function printStartMessage(): void + { + } + + protected function runServerProcess(string $command): void + { + $this->server = Mockery::mock(\Illuminate\Contracts\Process\InvokedProcess::class); + $this->server->shouldReceive('running')->once()->andReturn(false); + } + + protected function runViteProcess(): void + { + $this->viteProcessStarted = true; + } + + protected function openInBrowser(string $path = '/'): void + { + // + } + }; + } } /** From 2208e17955593a68ea4630ce178cf701329b242b Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 15:04:27 +0100 Subject: [PATCH 35/47] Suppress failed connection warnings --- packages/framework/src/Facades/Vite.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/framework/src/Facades/Vite.php b/packages/framework/src/Facades/Vite.php index a8dd219ebdf..4a8b7577c2c 100644 --- a/packages/framework/src/Facades/Vite.php +++ b/packages/framework/src/Facades/Vite.php @@ -20,7 +20,9 @@ public static function running(): bool // Check if Vite dev server is running by attempting to connect to it // Todo: Check performance on Windows (takes less than 1ms on macOS) - $server = @fsockopen('localhost', 5173, $errno, $errstr, 0.1); + set_error_handler(fn () => false); + $server = fsockopen('localhost', 5173, $errno, $errstr, 0.1); + restore_error_handler(); if ($server) { fclose($server); From 761d92a55df179c585892a41418d1b1d3c8864de Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Tue, 12 Nov 2024 15:12:58 +0100 Subject: [PATCH 36/47] Update Vite server to check if the port is free --- .../src/Console/Commands/ServeCommand.php | 19 +++++++++++++++++++ .../Feature/Commands/ServeCommandTest.php | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index 00a9628cce3..248ab4431aa 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -193,6 +193,25 @@ protected function getOpenCommand(string $osFamily): ?string protected function runViteProcess(): void { + if (! $this->isPortAvailable(5173)) { + throw new InvalidArgumentException( + 'Unable to start Vite server: Port 5173 is already in use. '. + 'Please stop any other Vite processes and try again.' + ); + } + $this->vite = Process::forever()->start('npm run dev'); } + + protected function isPortAvailable(int $port): bool + { + $socket = @fsockopen('localhost', $port, $errno, $errstr, 1); + if ($socket !== false) { + fclose($socket); + + return false; + } + + return true; + } } diff --git a/packages/framework/tests/Feature/Commands/ServeCommandTest.php b/packages/framework/tests/Feature/Commands/ServeCommandTest.php index d12916858a0..8e321328099 100644 --- a/packages/framework/tests/Feature/Commands/ServeCommandTest.php +++ b/packages/framework/tests/Feature/Commands/ServeCommandTest.php @@ -265,6 +265,17 @@ public function testHydeServeCommandWithViteOptionButViteNotRunning() ->assertExitCode(0); } + public function testHydeServeCommandWithViteOptionThrowsWhenPortIsInUse() + { + $socket = stream_socket_server('tcp://127.0.0.1:5173'); + + $this->artisan('serve --vite') + ->expectsOutputToContain('Unable to start Vite server: Port 5173 is already in use') + ->assertExitCode(1); + + stream_socket_shutdown($socket, STREAM_SHUT_RDWR); + } + protected function binaryPath(): string { return Hyde::path('vendor/hyde/realtime-compiler/bin/server.php'); From deb202cda7a4f01d755543fe17ec4fe509834ae3 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Thu, 14 Nov 2024 13:57:49 +0100 Subject: [PATCH 37/47] Conditionally write output --- packages/framework/src/Console/Commands/ServeCommand.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index 248ab4431aa..5abf027dd89 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -71,7 +71,11 @@ public function safeHandle(): int while ($this->server->running()) { if (isset($this->vite) && $this->vite->running()) { - $this->line($this->vite->latestOutput()); + $output = $this->vite->latestOutput(); + + if ($output) { + $this->output->write($output); + } } Sleep::usleep(100000); // 100ms From 41a79fe83cbeecaa99efd30d0645aa26d7bff43d Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Thu, 14 Nov 2024 14:45:12 +0100 Subject: [PATCH 38/47] Document Windows performance --- packages/framework/src/Facades/Vite.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/framework/src/Facades/Vite.php b/packages/framework/src/Facades/Vite.php index 4a8b7577c2c..912728d7487 100644 --- a/packages/framework/src/Facades/Vite.php +++ b/packages/framework/src/Facades/Vite.php @@ -19,7 +19,7 @@ public static function running(): bool } // Check if Vite dev server is running by attempting to connect to it - // Todo: Check performance on Windows (takes less than 1ms on macOS) + // Todo: Improve performance on Windows (takes less than 1ms on macOS, but around 100ms on Windows) set_error_handler(fn () => false); $server = fsockopen('localhost', 5173, $errno, $errstr, 0.1); restore_error_handler(); From 0daac5a05381c18799a5c64ea087436062f50a1f Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Thu, 14 Nov 2024 14:55:50 +0100 Subject: [PATCH 39/47] Document Windows issue --- packages/framework/src/Facades/Vite.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/framework/src/Facades/Vite.php b/packages/framework/src/Facades/Vite.php index 912728d7487..b778f928789 100644 --- a/packages/framework/src/Facades/Vite.php +++ b/packages/framework/src/Facades/Vite.php @@ -20,7 +20,7 @@ public static function running(): bool // Check if Vite dev server is running by attempting to connect to it // Todo: Improve performance on Windows (takes less than 1ms on macOS, but around 100ms on Windows) - set_error_handler(fn () => false); + set_error_handler(fn () => false); // Todo: This warning surpressor does not work on Windows $server = fsockopen('localhost', 5173, $errno, $errstr, 0.1); restore_error_handler(); From 91297e83feaf79400b2a01159926523e8728e24b Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Thu, 14 Nov 2024 15:22:40 +0100 Subject: [PATCH 40/47] Update RELEASE_NOTES.md --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7d14e72431f..832a0d88e65 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -33,6 +33,7 @@ This serves two purposes: - Added method `Asset::exists()` has to check if a media file exists. - Added a `Hyde::assets()` method to get all media file instances in the site. - Added new `npm run build` command for compiling frontend assets with Vite +- Added Vite HMR support for the realtime compiler in https://github.com/hydephp/develop/pull/2016 ### Changed @@ -131,6 +132,7 @@ This serves two purposes: #### Realtime Compiler - Simplified the asset file locator to only serve files from the media source directory in https://github.com/hydephp/develop/pull/2012 +- Added Vite HMR support in https://github.com/hydephp/develop/pull/2016 ### Upgrade Guide From ff1f6dc109a6dad21c6d36a2d18becafbcb918db Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Thu, 14 Nov 2024 15:58:43 +0100 Subject: [PATCH 41/47] Fix unintentional change --- packages/realtime-compiler/resources/dashboard.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/realtime-compiler/resources/dashboard.blade.php b/packages/realtime-compiler/resources/dashboard.blade.php index ba7301e8d83..d7bcf0fb77a 100644 --- a/packages/realtime-compiler/resources/dashboard.blade.php +++ b/packages/realtime-compiler/resources/dashboard.blade.php @@ -98,7 +98,7 @@ await navigator.clipboard.writeText(data); document.getElementById("copyPathToClipboardButtonIcon").style.display = 'none'; document.getElementById("copyPathToClipboardButtonIconSuccess").style.display = 'inline'; - await new Promise(resolve => setTimeout(resolve, 5173)).then(function () { + await new Promise(resolve => setTimeout(resolve, 3000)).then(function () { document.getElementById("copyPathToClipboardButtonIcon").style.display = 'inline'; document.getElementById("copyPathToClipboardButtonIconSuccess").style.display = 'none'; }); From 0da73bf24f751e5897a8755e65b7e9c08541580e Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Thu, 14 Nov 2024 15:59:50 +0100 Subject: [PATCH 42/47] Fix formatting --- packages/realtime-compiler/resources/vite-index-page.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/realtime-compiler/resources/vite-index-page.html b/packages/realtime-compiler/resources/vite-index-page.html index b6902af7381..aee5828f9c3 100644 --- a/packages/realtime-compiler/resources/vite-index-page.html +++ b/packages/realtime-compiler/resources/vite-index-page.html @@ -63,4 +63,4 @@

@endif From 313e60516fa01a7777acc1201a765363f90406ce Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Thu, 14 Nov 2024 16:23:22 +0100 Subject: [PATCH 47/47] Update _ide_helper.php --- _ide_helper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/_ide_helper.php b/_ide_helper.php index a838afac059..88203364b33 100644 --- a/_ide_helper.php +++ b/_ide_helper.php @@ -37,6 +37,7 @@ class Asset extends \Hyde\Facades\Asset {} class Author extends \Hyde\Facades\Author {} class Features extends \Hyde\Facades\Features {} class Config extends \Hyde\Facades\Config {} +class Vite extends \Hyde\Facades\Vite {} /** @mixin \Illuminate\Filesystem\Filesystem */ class Filesystem extends \Hyde\Facades\Filesystem {} class DataCollection extends \Hyde\Support\DataCollection {}