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] Improve the Vite integration #2060

Merged
merged 30 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
748664d
Remove unnecessary comment
caendesilva Dec 8, 2024
df47004
Unwrap function call
caendesilva Dec 8, 2024
f0029d1
Extract helper method from code comment
caendesilva Dec 8, 2024
dece097
Add strict types declaration
caendesilva Dec 8, 2024
5e7c05c
Convert concatenation to a scalar values
caendesilva Dec 8, 2024
4f5d228
Replace qualifier with an import
caendesilva Dec 8, 2024
5e653a9
Add inverse test
caendesilva Dec 8, 2024
5508d35
Improve confidence of test
caendesilva Dec 8, 2024
079913d
Move up and improve test
caendesilva Dec 8, 2024
ecbf8bd
Extract helper methods
caendesilva Dec 8, 2024
029a0c6
Apply fixes from StyleCI
StyleCIBot Dec 8, 2024
aab895a
Add todo
caendesilva Dec 8, 2024
3501102
Annotate the array types
caendesilva Dec 8, 2024
0e94056
Extract helper methods
caendesilva Dec 8, 2024
7e0016b
Better document the Vite system
caendesilva Dec 8, 2024
30b0939
Swap section order
caendesilva Dec 8, 2024
a511976
Support all CSS extensions like Laravel Vite uses
caendesilva Dec 8, 2024
7230be5
Improve accuracy of test
caendesilva Dec 8, 2024
59979e3
Use else if instead of if
caendesilva Dec 8, 2024
06d9641
Revert "Use else if instead of if"
caendesilva Dec 8, 2024
da2ba15
Add more tests
caendesilva Dec 8, 2024
d5ec63d
Extract helper to format the asset path
caendesilva Dec 8, 2024
4bc0f52
Use static instead of self
caendesilva Dec 8, 2024
8af55ed
Add helper to get single asset
caendesilva Dec 8, 2024
fb3865c
Simplify test
caendesilva Dec 8, 2024
bf88379
Throw exception if the asset type is not supported
caendesilva Dec 8, 2024
b048fae
Use only hotfiles instead of internal environment variable
caendesilva Dec 8, 2024
9ffbeed
Extract constant for CSS extensions
caendesilva Dec 8, 2024
f418e2b
Support more JavaScript extensions
caendesilva Dec 8, 2024
83264ca
Extract helper method for repeated code
caendesilva Dec 8, 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
51 changes: 51 additions & 0 deletions docs/creating-content/managing-assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,57 @@ Some extra component styles are organized into modular files in the HydeFront pa
To get you started quickly, all the styles are already compiled and minified into `_media/app.css`,
which will be copied to the `_site/media/app.css` directory when you run `php hyde build`.

## Vite

Hyde uses [Vite](https://vitejs.dev/) to compile assets. Vite is a build tool that aims to provide a faster and more efficient development experience for modern web projects.

### Why Vite?

HydePHP integrates Vite to compile assets such as CSS and JavaScript files. This integration ensures that your assets are processed efficiently, enhancing the development workflow by leveraging Vite's rapid build system.

#### Asset Management

**Development and Production Modes**

- **Development Mode**: Use `npm run dev` to start the Vite development HMR server, which provides fast live reloading and efficient compilation during development.
- **Production Mode**: Use `npm run build` for creating optimized, minified asset bundles ready for production deployment.

**Asset Compilation**:

- Assets are compiled from the `resources/assets` directory. The primary CSS file, `app.css`, is processed with TailwindCSS and other specified tools like PostCSS.
- Vite automatically processes all scripts and styles, outputting compiled files to the `_media` directory. These are copied to `_site/media` when the static site is built with `php hyde build`.

>warn Note that the HydePHP Vite integration only supports CSS and JavaScript files, if you try to load other file types, they will not be processed by Vite.

**Configuration**:
- You can customize Vite's behavior and output paths by modifying the pre-configured `vite.config.js` file in the project root directory.

### Hot Module Replacement (HMR)

Vite's HMR feature allows for instant updates to the browser without requiring a full page reload. This **only works** through the realtime compiler when the Vite development server is also running.

You can start both of these by running `npm run dev` and `php hyde serve` in separate terminals, or using the `--vite` flag with the serve command:

```bash
php hyde serve --vite
```

### Blade Integration

Hyde effortlessly integrates Vite with Blade views, allowing you to include compiled assets in your templates. The Blade components `hyde::layouts.styles` and `hyde::layouts.scripts` are already set up to load the compiled CSS and JavaScript files.

You can check if the Vite HMR server is running with `Vite::running()`, and you can include CSS and JavaScript resources with `Vite::asset('path')`, or `Vite::assets([])` to supply an array of paths.

**Example: Using Vite if the HMR server is enabled, or loading the compiled CSS file if not:**

```blade
@if(Vite::running())
{{ Vite::assets(['resources/assets/app.css']) }}
@else
<link rel="stylesheet" href="{{ asset('media/app.css') }}">
@endif
```

## Additional Information and Answers to Common Questions

### Is NodeJS/NPM Required for Using Hyde?
Expand Down
4 changes: 3 additions & 1 deletion packages/framework/src/Console/Commands/ServeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Hyde\Console\Commands;

use Closure;
use Hyde\Facades\Filesystem;
use Hyde\Hyde;
use Hyde\Facades\Config;
use Illuminate\Contracts\Process\InvokedProcess;
Expand Down Expand Up @@ -112,7 +113,6 @@ 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 @@ -204,6 +204,8 @@ protected function runViteProcess(): void
);
}

Filesystem::touch('app/storage/framework/cache/vite.hot');

$this->vite = Process::forever()->start('npm run dev');
}

Expand Down
66 changes: 52 additions & 14 deletions packages/framework/src/Facades/Vite.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,75 @@
namespace Hyde\Facades;

use Illuminate\Support\HtmlString;
use InvalidArgumentException;

/**
* Vite facade for handling Vite-related operations.
*/
class Vite
{
protected const CSS_EXTENSIONS = ['css', 'less', 'sass', 'scss', 'styl', 'stylus', 'pcss', 'postcss'];
protected const JS_EXTENSIONS = ['js', 'jsx', 'ts', 'tsx'];
Comment on lines +15 to +16
Copy link
Member Author

Choose a reason for hiding this comment

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

This is seriously overkill, but it doesn't really add any more complexity to add all these. While they might not work unless more media file extensions are registered, adding all these here future proofs things without needing to support customization here.


public static function running(): bool
{
// Check if Vite was enabled via the serve command
if (env('HYDE_SERVER_VITE') === 'enabled') {
return true;
}

// Check for Vite hot file
return Filesystem::exists('app/storage/framework/cache/vite.hot');
}

public static function asset(string $path): HtmlString
{
return static::assets([$path]);
}

/** @param array<string> $paths */
public static function assets(array $paths): HtmlString
{
$html = sprintf('<script src="http://localhost:5173/@vite/client" type="module"></script>');
$html = '<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);
}
$html .= static::formatAssetPath($path);
}

return new HtmlString($html);
}

/** @throws InvalidArgumentException If the asset type is not supported. */
protected static function formatAssetPath(string $path): string
{
if (static::isCssPath($path)) {
return static::formatStylesheetLink($path);
}

if (static::isJsPath($path)) {
return static::formatScriptInclude($path);
}

// We don't know how to handle other asset types, so we throw an exception to let the user know.
throw new InvalidArgumentException("Unsupported asset type for path: '$path'");
}

protected static function isCssPath(string $path): bool
{
return static::checkFileExtensionForPath($path, static::CSS_EXTENSIONS);
}

protected static function isJsPath(string $path): bool
{
return static::checkFileExtensionForPath($path, static::JS_EXTENSIONS);
}

protected static function checkFileExtensionForPath(string $path, array $extensions): bool
{
return preg_match('/\.('.implode('|', $extensions).')$/', $path) === 1;
}

protected static function formatStylesheetLink(string $path): string
{
return sprintf('<link rel="stylesheet" href="http://localhost:5173/%s">', $path);
}

protected static function formatScriptInclude(string $path): string
{
return sprintf('<script src="http://localhost:5173/%s" type="module"></script>', $path);
}
}
12 changes: 10 additions & 2 deletions packages/framework/tests/Feature/Commands/ServeCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ public function testWithFancyOutput()

public function testHydeServeCommandWithViteOption()
{
$this->cleanUpWhenDone('app/storage/framework/cache/vite.hot');

$mockViteProcess = mock(InvokedProcess::class);
$mockViteProcess->shouldReceive('running')
->once()
Expand All @@ -202,7 +204,7 @@ public function testHydeServeCommandWithViteOption()

Process::shouldReceive('env')
->once()
->with(['HYDE_SERVER_REQUEST_OUTPUT' => false, 'HYDE_SERVER_VITE' => 'enabled'])
->with(['HYDE_SERVER_REQUEST_OUTPUT' => false])
->andReturnSelf();

Process::shouldReceive('start')
Expand All @@ -224,10 +226,14 @@ public function testHydeServeCommandWithViteOption()
->expectsOutput('server output')
->expectsOutput('vite latest output')
->assertExitCode(0);

$this->assertFileExists('app/storage/framework/cache/vite.hot');
}

public function testHydeServeCommandWithViteOptionButViteNotRunning()
{
$this->cleanUpWhenDone('app/storage/framework/cache/vite.hot');

$mockViteProcess = mock(InvokedProcess::class);
$mockViteProcess->shouldReceive('running')
->once()
Expand All @@ -245,7 +251,7 @@ public function testHydeServeCommandWithViteOptionButViteNotRunning()

Process::shouldReceive('env')
->once()
->with(['HYDE_SERVER_REQUEST_OUTPUT' => false, 'HYDE_SERVER_VITE' => 'enabled'])
->with(['HYDE_SERVER_REQUEST_OUTPUT' => false])
->andReturnSelf();

Process::shouldReceive('start')
Expand All @@ -263,6 +269,8 @@ public function testHydeServeCommandWithViteOptionButViteNotRunning()
$this->artisan('serve --no-ansi --vite')
->expectsOutput('Starting the HydeRC server... Use Ctrl+C to stop')
->assertExitCode(0);

$this->assertFileExists('app/storage/framework/cache/vite.hot');
}

public function testHydeServeCommandWithViteOptionThrowsWhenPortIsInUse()
Expand Down
Loading
Loading