Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
2f9ad79
Create Vite.php
emmadesilva Nov 12, 2024
e3dc0a2
Create ViteFacadeTest.php
emmadesilva Nov 12, 2024
8aba46b
Create Vite server configuration
emmadesilva Nov 12, 2024
220fceb
Draft Vite option for the serve command
emmadesilva Nov 12, 2024
d67c699
Improve realtime compiler documentation
emmadesilva Nov 12, 2024
851421d
Method to print the Vite message
emmadesilva Nov 12, 2024
d2afcea
Run processes in parallel
emmadesilva Nov 12, 2024
6ad2b04
Stream both process outputs
emmadesilva Nov 12, 2024
b6361ab
Create vite-index-page.html
emmadesilva Nov 12, 2024
c87203a
Render Vite index page
emmadesilva Nov 12, 2024
ed2404d
Draft facade methods
emmadesilva Nov 12, 2024
f5d6c84
Alias Vite facade
emmadesilva Nov 12, 2024
e59cecf
Add strict types declaration
emmadesilva Nov 12, 2024
93bfbbc
Implement the Vite asset method
emmadesilva Nov 12, 2024
2683f86
Serve Vite assets when running
emmadesilva Nov 12, 2024
58a7f7a
Implement the Vite connection checker
emmadesilva Nov 12, 2024
86e0180
Use much faster environment variable for Vite status
emmadesilva Nov 12, 2024
b083300
Flush output
emmadesilva Nov 12, 2024
da82fa6
Start over with Vite server
emmadesilva Nov 12, 2024
310407e
Simpler Vite process handler
emmadesilva Nov 12, 2024
d0fc208
Run server asynchronously
emmadesilva Nov 12, 2024
b6d96c4
Sleep while server is running
emmadesilva Nov 12, 2024
6d40de6
Handle output from sleep loop
emmadesilva Nov 12, 2024
6ea538f
Declare properties
emmadesilva Nov 12, 2024
6a19d1f
Type against the contract
emmadesilva Nov 12, 2024
f170366
Sleep for 100ms instead of 1 second
emmadesilva Nov 12, 2024
d935511
Remove unused Vite start message helper
emmadesilva Nov 12, 2024
406e8ae
Add Vite information to output start message
emmadesilva Nov 12, 2024
8419a68
Support standalone Vite servers
emmadesilva Nov 12, 2024
57a22d9
Use default Vite port
emmadesilva Nov 12, 2024
f1c421e
Add widths for layout to work without network
emmadesilva Nov 12, 2024
c0d67ff
Update tests for asynchronous server process
emmadesilva Nov 12, 2024
704f67c
Use the sleep facade
emmadesilva Nov 12, 2024
b03ec7f
Add tests to cover introduced code
emmadesilva Nov 12, 2024
2208e17
Suppress failed connection warnings
emmadesilva Nov 12, 2024
761d92a
Update Vite server to check if the port is free
emmadesilva Nov 12, 2024
deb202c
Conditionally write output
emmadesilva Nov 14, 2024
41a79fe
Document Windows performance
emmadesilva Nov 14, 2024
0daac5a
Document Windows issue
emmadesilva Nov 14, 2024
91297e8
Update RELEASE_NOTES.md
emmadesilva Nov 14, 2024
3687cea
Merge branch 'new-asset-system' into vite-integration
emmadesilva Nov 14, 2024
fa4a631
Merge branch 'new-asset-system' into vite-integration
emmadesilva Nov 14, 2024
2e7f526
Merge branch 'new-asset-system' into vite-integration
emmadesilva Nov 14, 2024
ff1f6dc
Fix unintentional change
emmadesilva Nov 14, 2024
0da73bf
Fix formatting
emmadesilva Nov 14, 2024
9dc44d1
Normalize formatting
emmadesilva Nov 14, 2024
5ed9f6b
Update RELEASE_NOTES.md
emmadesilva Nov 14, 2024
e26941d
Mark feature as experimental
emmadesilva Nov 14, 2024
2a480c2
Add Vite HMR support for the app scripts
emmadesilva Nov 14, 2024
313e605
Update _ide_helper.php
emmadesilva Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ 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 a Vite HMR support for the realtime compiler in https://github.com/hydephp/develop/pull/2016
- Added Vite facade in https://github.com/hydephp/develop/pull/2016

### Changed

Expand Down Expand Up @@ -131,6 +133,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

Expand Down
1 change: 1 addition & 0 deletions _ide_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down
1 change: 1 addition & 0 deletions app/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions docs/extensions/realtime-compiler.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ This will start a local development server at `http://localhost:8080`
- `--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

Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/console-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Run the static site builder for a single file
<a name="serve" style="display: inline-block; position: absolute; margin-top: -5rem;"></a>

```bash
php hyde serve [--host [HOST]] [--port [PORT]]
php hyde serve [--host [HOST]] [--port [PORT]] [--vite]
```

Start the realtime compiler server.
Expand Down
4 changes: 3 additions & 1 deletion packages/framework/resources/views/layouts/scripts.blade.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{{-- The compiled Vite scripts --}}
@if(Asset::exists('app.js'))
@if(Vite::running())
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May make more sense to have this alongside the CSS include since the script is deferred anyways, but not sure how to handle that best with the partials since it would fit better in the head component, and don't want to make such a big change here.

{{ Vite::assets(['resources/assets/app.js']) }}
@elseif(Asset::exists('app.js'))
<script defer src="{{ Asset::get('app.js') }}"></script>
@endif

Expand Down
25 changes: 15 additions & 10 deletions packages/framework/resources/views/layouts/styles.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@
<style>[x-cloak] {display: none!important}</style>

{{-- The compiled Tailwind/App styles --}}
@if(config('hyde.load_app_styles_from_cdn', false))
<link rel="stylesheet" href="{{ HydeFront::cdnLink('app.css') }}">
@elseif(Asset::exists('app.css'))
<link rel="stylesheet" href="{{ Asset::get('app.css') }}">
@endif
@if(Vite::running())
{{ Vite::assets(['resources/assets/app.css']) }}
@else
@if(config('hyde.load_app_styles_from_cdn', false))
<link rel="stylesheet" href="{{ HydeFront::cdnLink('app.css') }}">
@elseif(Asset::exists('app.css'))
<link rel="stylesheet" href="{{ Asset::get('app.css') }}">
@endif

Comment on lines +7 to +13
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make more sense if we just automatically enabled Vite when using Asset::get and Vite is running?


{{-- Dynamic TailwindCSS Play CDN --}}
@if(config('hyde.use_play_cdn', false))
<script src="https://cdn.tailwindcss.com?plugins=typography"></script>
<script>tailwind.config = { {!! HydeFront::injectTailwindConfig() !!} }</script>
<script>console.warn('The HydePHP TailwindCSS Play CDN is enabled. This is for development purposes only and should not be used in production.', 'See https://hydephp.com/docs/1.x/managing-assets');</script>
{{-- Dynamic TailwindCSS Play CDN --}}
@if(config('hyde.use_play_cdn', false))
<script src="https://cdn.tailwindcss.com?plugins=typography"></script>
<script>tailwind.config = { {!! HydeFront::injectTailwindConfig() !!} }</script>
<script>console.warn('The HydePHP TailwindCSS Play CDN is enabled. This is for development purposes only and should not be used in production.', 'See https://hydephp.com/docs/1.x/managing-assets');</script>
@endif
@endif

{{-- Add any extra styles to include after the others --}}
Expand Down
56 changes: 53 additions & 3 deletions packages/framework/src/Console/Commands/ServeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
use Closure;
use Hyde\Hyde;
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;
Expand Down Expand Up @@ -35,13 +37,17 @@ 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 */
protected $description = 'Start the realtime compiler server';

protected ConsoleOutput $console;

protected InvokedProcess $server;
protected InvokedProcess $vite;

public function safeHandle(): int
{
$this->configureOutput();
Expand All @@ -51,11 +57,29 @@ 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);

while ($this->server->running()) {
if (isset($this->vite) && $this->vite->running()) {
$output = $this->vite->latestOutput();

if ($output) {
$this->output->write($output);
}
}

Sleep::usleep(100000); // 100ms
}

return Command::SUCCESS;
}
Expand All @@ -77,7 +101,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
Expand All @@ -88,6 +112,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,
]);
}

Expand Down Expand Up @@ -169,4 +194,29 @@ protected function getOpenCommand(string $osFamily): ?string
default => null
};
}

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');
}

/** @experimental This feature may be removed before the final release. */
protected function isPortAvailable(int $port): bool
{
$socket = @fsockopen('localhost', $port, $errno, $errstr, 1);
if ($socket !== false) {
fclose($socket);

return false;
}

return true;
}
}
52 changes: 52 additions & 0 deletions packages/framework/src/Facades/Vite.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Hyde\Facades;

use Illuminate\Support\HtmlString;

/**
* Vite facade for handling Vite-related operations.
*/
class Vite
{
public static function running(): bool
{
// 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: Improve performance on Windows (takes less than 1ms on macOS, but around 100ms on Windows)
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();

if ($server) {
fclose($server);

return true;
}

return false;
}

public static function assets(array $paths): HtmlString
{
$html = sprintf('<script src="http://localhost:5173/@vite/client" type="module"></script>');

foreach ($paths as $path) {
if (str_ends_with($path, '.css')) {
$html .= sprintf('<link rel="stylesheet" href="http://localhost:5173/%s">', $path);
}

if (str_ends_with($path, '.js')) {
$html .= sprintf('<script src="http://localhost:5173/%s" type="module"></script>', $path);
}
}

return new HtmlString($html);
}
}
106 changes: 104 additions & 2 deletions packages/framework/tests/Feature/Commands/ServeCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()
Expand All @@ -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... Use Ctrl+C to stop')
Expand All @@ -174,6 +180,102 @@ 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... Use 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... Use Ctrl+C to stop')
->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');
Expand Down
Loading