Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2.x] Add Vite Realtime Compiler integration for HMR #2016

Merged
merged 50 commits into from
Nov 14, 2024

Conversation

caendesilva
Copy link
Member

@caendesilva caendesilva commented Nov 12, 2024

Add Vite Realtime Compiler integration for HMR

Description

This PR integrates the Vite with the Realtime Compiler for Hot Module Replacement for a highly efficient development workflow by enabling real-time updates without needing a full page reload.

Key Changes

  • Added Vite facade: A new Vite facade is added for handling Vite-related operations, allowing for easy access to the Vite server status and for generating Vite asset tags.
  • Vite integration in serve command: The serve command now has a --vite option to enable Vite for Hot Module Replacement (HMR). When enabled, the command starts a Vite development server along with the HydeRC server.
  • Asynchronous process handling: The serve command now runs the server and Vite processes asynchronously, allowing for parallel execution and more responsive output.
  • Output streaming: The console output from both the server and Vite processes is streamed directly to the terminal.
  • Port availability check: The serve command also checks if the Vite server port is available before starting the Vite development server, preventing conflicts and providing useful error messages.

This significantly improves the developer experience by allowing for fast, iterative development with instant feedback via HMR.

Future Tasks

@caendesilva
Copy link
Member Author

Can probably add nicer output, but maybe in a later PR.

image

@caendesilva caendesilva mentioned this pull request Nov 14, 2024
14 tasks
@caendesilva
Copy link
Member Author

@caendesilva
Copy link
Member Author

We have this latter part to automatically connect to a Vite server directly run through npm run dev instead of php hyde serve --vite.

// 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;

However, this introduces an unacceptable real-time compiler performance penalty for Windows users, where each page load now takes 100ms longer as this check is performed every time a page is served.

Here are my ideas initial ideas to adress this.

  • While we can't cache this as a static property since each previewed page is run in a new process, we could, from the realtime compiler serve command, do this check there and add the env var if running. But this means that Vite would only work if that command was run before the serve command, unless we use the output loop to periodically check for a Vite server, but that would then require the web server to restart in order to get the new env var.
  • Add a "hotfile" like Laravel uses?
  • Vite manifest trickery?

@caendesilva
Copy link
Member Author

We have this latter part to automatically connect to a Vite server directly run through npm run dev instead of php hyde serve --vite.

// 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;

However, this introduces an unacceptable real-time compiler performance penalty for Windows users, where each page load now takes 100ms longer as this check is performed every time a page is served.

Here are my ideas initial ideas to adress this.

  • While we can't cache this as a static property since each previewed page is run in a new process, we could, from the realtime compiler serve command, do this check there and add the env var if running. But this means that Vite would only work if that command was run before the serve command, unless we use the output loop to periodically check for a Vite server, but that would then require the web server to restart in order to get the new env var.
  • Add a "hotfile" like Laravel uses?
  • Vite manifest trickery?

Claude Analysis

Let me analyze the options and propose a solution:

Analysis of Options

  1. Environment Variable from Serve Command
  • Pros: Simple to implement
  • Cons: Requires server restart, not reliable for direct Vite usage
  1. Hot File Approach (Laravel-style)
  • Pros:
    • Fast (file_exists check is much quicker than fsockopen)
    • Works across processes
    • Can be updated in real-time
  • Cons:
    • Needs cleanup handling
  1. Vite Manifest
  • Pros: Already part of Vite workflow
  • Cons: Not suitable as it's only created after build
  1. New Option: Combined Approach
  • Use hot file created by serve command
  • Periodic check in output loop for direct Vite usage
  • File-based check instead of socket connection

Recommended Solution: Hot File Approach

This is the best option because:

  1. Eliminates the performance penalty of socket connections
  2. Works across processes
  3. Supports both serve command and direct Vite usage
  4. Similar to proven Laravel approach

Development Plan

  1. Create ViteManager Class
namespace Hyde\Framework\Services;

class ViteManager
{
    protected static function getHotFile(): string
    {
        return Hyde::path('storage/framework/cache/vite.hot');
    }

    public static function createHotFile(): void
    {
        file_put_contents(static::getHotFile(), time());
    }

    public static function removeHotFile(): void
    {
        @unlink(static::getHotFile());
    }

    public static function isRunning(): bool
    {
        return file_exists(static::getHotFile());
    }
}
  1. Update Vite Facade
public static function running(): bool
{
    // Check if Vite was enabled via the serve command
    if (env('HYDE_SERVER_VITE') === 'enabled') {
        return true;
    }

    return ViteManager::isRunning();
}
  1. Update ServeCommand
    Reference:
    protected ConsoleOutput $console;

    protected InvokedProcess $server;
    protected InvokedProcess $vite;

    public function safeHandle(): int
    {
        $this->configureOutput();
        $this->printStartMessage();

        if ($this->option('open') !== 'false') {
            $this->openInBrowser((string) $this->option('open'));
        }

        $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;
    }

Add hot file management:

protected function runViteProcess(): void
{
    ViteManager::createHotFile();
    
    // Existing Vite process code...
    
    register_shutdown_function(function () {
        ViteManager::removeHotFile();
    });
}
  1. Add Periodic Check in Output Loop
while ($this->server->running()) {
    if (isset($this->vite) && $this->vite->running()) {
        ViteManager::createHotFile();
    } else {
        ViteManager::removeHotFile();
    }
    
    // Existing output handling...
}

Implementation Benefits

  1. Windows performance improves significantly as file_exists() is much faster than fsockopen()
  2. Supports both php hyde serve --vite and direct npm run dev usage
  3. Auto-cleanup through shutdown function
  4. No need for server restarts
  5. Maintains current functionality while improving performance

@caendesilva caendesilva mentioned this pull request Nov 14, 2024
87 tasks
packages/realtime-compiler/resources/dashboard.blade.php Outdated Show resolved Hide resolved
Comment on lines +21 to +37
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();
}
});
},
},
],
Copy link
Member Author

Choose a reason for hiding this comment

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

Eventually we may want to extract a NPM plugin for this

Comment on lines +7 to +13
@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

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?

@@ -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.

@caendesilva caendesilva marked this pull request as ready for review November 14, 2024 15:26
@caendesilva caendesilva merged commit ec1583a into new-asset-system Nov 14, 2024
12 checks passed
@caendesilva caendesilva deleted the vite-integration branch November 14, 2024 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant