diff --git a/src/Illuminate/Foundation/Vite.php b/src/Illuminate/Foundation/Vite.php index 7a137e10db8a..98a016496492 100644 --- a/src/Illuminate/Foundation/Vite.php +++ b/src/Illuminate/Foundation/Vite.php @@ -3,11 +3,12 @@ namespace Illuminate\Foundation; use Exception; +use Illuminate\Contracts\Support\Htmlable; use Illuminate\Support\Collection; use Illuminate\Support\HtmlString; use Illuminate\Support\Str; -class Vite +class Vite implements Htmlable { /** * The Content Security Policy nonce to apply to all generated tags. @@ -23,6 +24,27 @@ class Vite */ protected $integrityKey = 'integrity'; + /** + * The configured entry points. + * + * @var array + */ + protected $entryPoints = []; + + /** + * The path to the "hot" file. + * + * @var string|null + */ + protected $hotFile; + + /** + * The path to the build directory. + * + * @var string + */ + protected $buildDirectory = 'build'; + /** * The script tag attributes resolvers. * @@ -78,6 +100,55 @@ public function useIntegrityKey($key) return $this; } + /** + * Set the Vite entry points. + * + * @param array $entryPoints + * @return $this + */ + public function withEntryPoints($entryPoints) + { + $this->entryPoints = $entryPoints; + + return $this; + } + + /** + * Get the Vite "hot" file path. + * + * @return string + */ + protected function hotFile() + { + return $this->hotFile ?? public_path('/hot'); + } + + /** + * Set the Vite "hot" file path. + * + * @param string $path + * @return $this + */ + public function useHotFile($path) + { + $this->hotFile = $path; + + return $this; + } + + /** + * Set the Vite build directory. + * + * @param string $path + * @return $this + */ + public function useBuildDirectory($path) + { + $this->buildDirectory = $path; + + return $this; + } + /** * Use the given callback to resolve attributes for script tags. * @@ -116,15 +187,15 @@ public function useStyleTagAttributes($attributes) * Generate Vite tags for an entrypoint. * * @param string|string[] $entrypoints - * @param string $buildDirectory + * @param string|null $buildDirectory * @return \Illuminate\Support\HtmlString * * @throws \Exception */ - public function __invoke($entrypoints, $buildDirectory = 'build') + public function __invoke($entrypoints, $buildDirectory = null) { $entrypoints = collect($entrypoints); - $buildDirectory = Str::start($buildDirectory, '/'); + $buildDirectory ??= $this->buildDirectory; if ($this->isRunningHot()) { return new HtmlString( @@ -396,7 +467,7 @@ public function reactRefresh() */ protected function hotAsset($asset) { - return rtrim(file_get_contents(public_path('/hot'))).'/'.$asset; + return rtrim(file_get_contents($this->hotFile())).'/'.$asset; } /** @@ -406,8 +477,10 @@ protected function hotAsset($asset) * @param string|null $buildDirectory * @return string */ - public function asset($asset, $buildDirectory = 'build') + public function asset($asset, $buildDirectory = null) { + $buildDirectory ??= $this->buildDirectory; + if ($this->isRunningHot()) { return $this->hotAsset($asset); } @@ -427,7 +500,7 @@ public function asset($asset, $buildDirectory = 'build') */ protected function manifest($buildDirectory) { - $path = public_path($buildDirectory.'/manifest.json'); + $path = $this->manifestPath($buildDirectory); if (! isset(static::$manifests[$path])) { if (! is_file($path)) { @@ -440,6 +513,17 @@ protected function manifest($buildDirectory) return static::$manifests[$path]; } + /** + * Get the path to the manifest file for the given build directory. + * + * @param string $buildDirectory + * @return string + */ + protected function manifestPath($buildDirectory) + { + return public_path($buildDirectory.'/manifest.json'); + } + /** * Get the chunk for the given entry point / asset. * @@ -465,6 +549,16 @@ protected function chunk($manifest, $file) */ protected function isRunningHot() { - return is_file(public_path('/hot')); + return is_file($this->hotFile()); + } + + /** + * Get the Vite tag content as a string of HTML. + * + * @return string + */ + public function toHtml() + { + return $this->__invoke($this->entryPoints)->toHtml(); } } diff --git a/src/Illuminate/Support/Facades/Vite.php b/src/Illuminate/Support/Facades/Vite.php index 052beee9b1b2..8b3617ccb134 100644 --- a/src/Illuminate/Support/Facades/Vite.php +++ b/src/Illuminate/Support/Facades/Vite.php @@ -6,9 +6,12 @@ * @method static string useCspNonce(?string $nonce = null) * @method static string|null cspNonce() * @method static string asset(string $asset, string|null $buildDirectory) + * @method static \Illuminate\Foundation\Vite useBuildDirectory(string $path) + * @method static \Illuminate\Foundation\Vite useHotFile(string $path) * @method static \Illuminate\Foundation\Vite useIntegrityKey(string|false $key) * @method static \Illuminate\Foundation\Vite useScriptTagAttributes(callable|array $callback) * @method static \Illuminate\Foundation\Vite useStyleTagAttributes(callable|array $callback) + * @method static \Illuminate\Foundation\Vite withEntryPoints(array $entryPoints) * * @see \Illuminate\Foundation\Vite */ diff --git a/tests/Foundation/FoundationViteTest.php b/tests/Foundation/FoundationViteTest.php index 873f22954959..86fd067423ed 100644 --- a/tests/Foundation/FoundationViteTest.php +++ b/tests/Foundation/FoundationViteTest.php @@ -192,8 +192,7 @@ public function testItCanInjectIntegrityWhenPresentInManifest() $result->toHtml() ); - unlink(public_path("{$buildDir}/manifest.json")); - rmdir(public_path($buildDir)); + $this->cleanViteManifest($buildDir); } public function testItCanInjectIntegrityWhenPresentInManifestForCss() @@ -228,8 +227,7 @@ public function testItCanInjectIntegrityWhenPresentInManifestForCss() $result->toHtml() ); - unlink(public_path("{$buildDir}/manifest.json")); - rmdir(public_path($buildDir)); + $this->cleanViteManifest($buildDir); } public function testItCanInjectIntegrityWhenPresentInManifestForImportedCss() @@ -264,8 +262,7 @@ public function testItCanInjectIntegrityWhenPresentInManifestForImportedCss() $result->toHtml() ); - unlink(public_path("{$buildDir}/manifest.json")); - rmdir(public_path($buildDir)); + $this->cleanViteManifest($buildDir); } public function testItCanSpecifyIntegrityKey() @@ -291,8 +288,7 @@ public function testItCanSpecifyIntegrityKey() $result->toHtml() ); - unlink(public_path("{$buildDir}/manifest.json")); - rmdir(public_path($buildDir)); + $this->cleanViteManifest($buildDir); } public function testItCanSpecifyArbitraryAttributesForScriptTagsWhenBuilt() @@ -539,6 +535,55 @@ public function testItThrowsWhenUnableToFindAssetChunkInBuildMode() ViteFacade::asset('resources/js/missing.js'); } + public function testViteCanSetEntryPointsWithFluentBuilder() + { + $this->makeViteManifest(); + + $vite = app(Vite::class); + + $this->assertSame('', $vite->toHtml()); + + $vite->withEntryPoints(['resources/js/app.js']); + + $this->assertSame( + '', + $vite->toHtml() + ); + } + + public function testViteCanOverrideBuildDirectory() + { + $this->makeViteManifest(null, 'custom-build'); + + $vite = app(Vite::class); + + $vite->withEntryPoints(['resources/js/app.js'])->useBuildDirectory('custom-build'); + + $this->assertSame( + '', + $vite->toHtml() + ); + + $this->cleanViteManifest('custom-build'); + } + + public function testViteCanOverrideHotFilePath() + { + $this->makeViteHotFile('cold'); + + $vite = app(Vite::class); + + $vite->withEntryPoints(['resources/js/app.js'])->useHotFile('cold'); + + $this->assertSame( + '' + .'', + $vite->toHtml() + ); + + $this->cleanViteHotFile('cold'); + } + protected function makeViteManifest($contents = null, $path = 'build') { app()->singleton('path.public', fn () => __DIR__); @@ -582,28 +627,32 @@ protected function makeViteManifest($contents = null, $path = 'build') file_put_contents(public_path("{$path}/manifest.json"), $manifest); } - protected function cleanViteManifest() + protected function cleanViteManifest($path = 'build') { - if (file_exists(public_path('build/manifest.json'))) { - unlink(public_path('build/manifest.json')); + if (file_exists(public_path("{$path}/manifest.json"))) { + unlink(public_path("{$path}/manifest.json")); } - if (file_exists(public_path('build'))) { - rmdir(public_path('build')); + if (file_exists(public_path($path))) { + rmdir(public_path($path)); } } - protected function makeViteHotFile() + protected function makeViteHotFile($path = null) { app()->singleton('path.public', fn () => __DIR__); - file_put_contents(public_path('hot'), 'http://localhost:3000'); + $path ??= public_path('hot'); + + file_put_contents($path, 'http://localhost:3000'); } - protected function cleanViteHotFile() + protected function cleanViteHotFile($path = null) { - if (file_exists(public_path('hot'))) { - unlink(public_path('hot')); + $path ??= public_path('hot'); + + if (file_exists($path)) { + unlink($path); } } }