Skip to content

[9.x] Vite #42785

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

Merged
merged 14 commits into from
Jun 22, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@

use Closure;
use Illuminate\Foundation\Mix;
use Illuminate\Foundation\Vite;
use Mockery;

trait InteractsWithContainer
{
/**
* The original Vite handler.
*
* @var \Illuminate\Foundation\Vite|null
*/
protected $originalVite;

/**
* The original Laravel Mix handler.
*
Expand Down Expand Up @@ -90,6 +98,38 @@ protected function forgetMock($abstract)
return $this;
}

/**
* Register an empty handler for Vite in the container.
*
* @return $this
*/
protected function withoutVite()
{
if ($this->originalVite == null) {
$this->originalVite = app(Vite::class);
}

$this->swap(Vite::class, function () {
return '';
});

return $this;
}

/**
* Restore Vite in the container.
*
* @return $this
*/
protected function withVite()
{
if ($this->originalVite) {
$this->app->instance(Vite::class, $this->originalVite);
}

return $this;
}

/**
* Register an empty handler for Laravel Mix in the container.
*
Expand All @@ -109,7 +149,7 @@ protected function withoutMix()
}

/**
* Register an empty handler for Laravel Mix in the container.
* Restore Laravel Mix in the container.
*
* @return $this
*/
Expand Down
157 changes: 157 additions & 0 deletions src/Illuminate/Foundation/Vite.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

namespace Illuminate\Foundation;

use Exception;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;

class Vite
{
/**
* Generate Vite tags for an entrypoint.
*
* @param string|string[] $entrypoints
* @param string $buildDirectory
* @return \Illuminate\Support\HtmlString
*
* @throws \Exception
*/
public function __invoke($entrypoints, $buildDirectory = 'build')
{
static $manifests = [];

$entrypoints = collect($entrypoints);
$buildDirectory = Str::start($buildDirectory, '/');

if (is_file(public_path('/hot'))) {
$url = rtrim(file_get_contents(public_path('/hot')));

return new HtmlString(
$entrypoints
->map(fn ($entrypoint) => $this->makeTag("{$url}/{$entrypoint}"))
->prepend($this->makeScriptTag("{$url}/@vite/client"))
->join('')
);
}

$manifestPath = public_path($buildDirectory.'/manifest.json');

if (! isset($manifests[$manifestPath])) {
if (! is_file($manifestPath)) {
throw new Exception("Vite manifest not found at: {$manifestPath}");
}

$manifests[$manifestPath] = json_decode(file_get_contents($manifestPath), true);
}

$manifest = $manifests[$manifestPath];

$tags = collect();

foreach ($entrypoints as $entrypoint) {
if (! isset($manifest[$entrypoint])) {
throw new Exception("Unable to locate file in Vite manifest: {$entrypoint}.");
}

$tags->push($this->makeTag(asset("{$buildDirectory}/{$manifest[$entrypoint]['file']}")));

if (isset($manifest[$entrypoint]['css'])) {
foreach ($manifest[$entrypoint]['css'] as $css) {
$tags->push($this->makeStylesheetTag(asset("{$buildDirectory}/{$css}")));
}
}

if (isset($manifest[$entrypoint]['imports'])) {
foreach ($manifest[$entrypoint]['imports'] as $import) {
if (isset($manifest[$import]['css'])) {
foreach ($manifest[$import]['css'] as $css) {
$tags->push($this->makeStylesheetTag(asset("{$buildDirectory}/{$css}")));
}
}
}
}
}

[$stylesheets, $scripts] = $tags->partition(fn ($tag) => str_starts_with($tag, '<link'));

return new HtmlString($stylesheets->join('').$scripts->join(''));
}

/**
* Generate React refresh runtime script.
*
* @return \Illuminate\Support\HtmlString|void
*/
public function reactRefresh()
{
if (! is_file(public_path('/hot'))) {
return;
}

$url = rtrim(file_get_contents(public_path('/hot')));

return new HtmlString(
sprintf(
<<<'HTML'
<script type="module">
import RefreshRuntime from '%s/@react-refresh'
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
HTML,
$url
)
);
}

/**
* Generate an appropriate tag for the given URL.
*
* @param string $url
* @return string
*/
protected function makeTag($url)
{
if ($this->isCssPath($url)) {
return $this->makeStylesheetTag($url);
}

return $this->makeScriptTag($url);
}

/**
* Generate a script tag for the given URL.
*
* @param string $url
* @return string
*/
protected function makeScriptTag($url)
{
return sprintf('<script type="module" src="%s"></script>', $url);
}

/**
* Generate a stylesheet tag for the given URL.
*
* @param string $url
* @return string
*/
protected function makeStylesheetTag($url)
{
return sprintf('<link rel="stylesheet" href="%s" />', $url);
}

/**
* Determine whether the given path is a CSS file.
*
* @param string $path
* @return bool
*/
protected function isCssPath($path)
{
return preg_match('/\.(css|less|sass|scss|styl|stylus|pcss|postcss)$/', $path) === 1;
}
}
29 changes: 29 additions & 0 deletions src/Illuminate/View/Compilers/Concerns/CompilesHelpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Illuminate\View\Compilers\Concerns;

use Illuminate\Foundation\Vite;

trait CompilesHelpers
{
/**
Expand Down Expand Up @@ -46,4 +48,31 @@ protected function compileMethod($method)
{
return "<?php echo method_field{$method}; ?>";
}

/**
* Compile the "vite" statements into valid PHP.
*
* @param ?string $arguments
* @return string
*/
protected function compileVite($arguments)
{
$arguments ??= '()';

$class = Vite::class;

return "<?php echo app('$class'){$arguments}; ?>";
}

/**
* Compile the "viteReactRefresh" statements into valid PHP.
*
* @return string
*/
protected function compileViteReactRefresh()
{
$class = Vite::class;

return "<?php echo app('$class')->reactRefresh(); ?>";
}
}
Loading