diff --git a/composer.json b/composer.json index 6cf6f0c4..75908c80 100644 --- a/composer.json +++ b/composer.json @@ -34,6 +34,7 @@ "symfony/console": "^6.2|^7.0" }, "require-dev": { + "guzzlehttp/guzzle": "^7.2", "roave/security-advisories": "dev-master", "orchestra/testbench": "^8.0|^9.2|^10.0", "mockery/mockery": "^1.3.3", @@ -52,4 +53,4 @@ }, "minimum-stability": "dev", "prefer-stable": true -} +} \ No newline at end of file diff --git a/src/Ssr/HttpGateway.php b/src/Ssr/HttpGateway.php index 9f777454..7b1f3850 100644 --- a/src/Ssr/HttpGateway.php +++ b/src/Ssr/HttpGateway.php @@ -3,7 +3,9 @@ namespace Inertia\Ssr; use Exception; +use Illuminate\Http\Client\StrayRequestException; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Vite; class HttpGateway implements Gateway { @@ -12,15 +14,17 @@ class HttpGateway implements Gateway */ public function dispatch(array $page): ?Response { - if (! config('inertia.ssr.enabled', true) || ! (new BundleDetector)->detect()) { + if (! config('inertia.ssr.enabled', true) || ! ($url = $this->getHttpUrl())) { return null; } - $url = str_replace('/render', '', rtrim(config('inertia.ssr.url', 'http://127.0.0.1:13714'), '/')).'/render'; - try { $response = Http::post($url, $page)->throw()->json(); } catch (Exception $e) { + if ($e instanceof StrayRequestException) { + throw $e; + } + return null; } @@ -33,4 +37,27 @@ public function dispatch(array $page): ?Response $response['body'] ); } + + /** + * Use the Vite asset URL if Vite is running in hot mode, otherwise + * return the SSR URL from the configuration if the bundle is detected. + */ + public function getHttpUrl(): ?string + { + if (Vite::isRunningHot()) { + return Vite::asset('render'); + } elseif ((new BundleDetector)->detect()) { + return $this->getSsrUrl(); + } + + return null; + } + + /** + * Get the SSR URL from the configuration, ensuring it ends with '/render'. + */ + public function getSsrUrl(): ?string + { + return str_replace('/render', '', rtrim(config('inertia.ssr.url', 'http://127.0.0.1:13714'), '/')).'/render'; + } } diff --git a/tests/DirectiveTest.php b/tests/DirectiveTest.php index f1afdd07..40e8bf73 100644 --- a/tests/DirectiveTest.php +++ b/tests/DirectiveTest.php @@ -30,8 +30,6 @@ class DirectiveTest extends TestCase /** * Example Page Objects. */ - protected const EXAMPLE_PAGE_OBJECT = ['component' => 'Foo/Bar', 'props' => ['foo' => 'bar'], 'url' => '/test', 'version' => '', 'encryptHistory' => false, 'clearHistory' => false]; - protected function setUp(): void { parent::setUp(); diff --git a/tests/HttpGatewayTest.php b/tests/HttpGatewayTest.php new file mode 100644 index 00000000..1fb16c8e --- /dev/null +++ b/tests/HttpGatewayTest.php @@ -0,0 +1,126 @@ +gateway = new HttpGateway; + + Http::preventStrayRequests(); + } + + public function test_it_returns_null_when_ssr_is_disabled() + { + config([ + 'inertia.ssr.enabled' => false, + 'inertia.ssr.bundle' => __DIR__.'/Stubs/bundle.js', + ]); + + Vite::shouldReceive('isRunningHot')->never(); + + $this->assertNull($this->gateway->dispatch(['page' => self::EXAMPLE_PAGE_OBJECT])); + } + + public function test_it_returns_null_when_no_bundle_file_is_detected_and_vite_is_not_running() + { + config([ + 'inertia.ssr.enabled' => true, + 'inertia.ssr.bundle' => null, + ]); + + Vite::shouldReceive('isRunningHot')->andReturn(false); + + $this->assertNull($this->gateway->dispatch(['page' => self::EXAMPLE_PAGE_OBJECT])); + } + + public function test_it_uses_the_configured_http_url_when_the_bundle_file_is_detected() + { + config([ + 'inertia.ssr.enabled' => true, + 'inertia.ssr.bundle' => __DIR__.'/Stubs/bundle.js', + ]); + + Vite::shouldReceive('isRunningHot')->andReturn(false); + + Http::fake([ + $this->gateway->getHttpUrl() => Http::response(json_encode([ + 'head' => ['SSR Test', ''], + 'body' => '
SSR Response
', + ])), + ]); + + $this->assertNotNull( + $response = $this->gateway->dispatch(['page' => self::EXAMPLE_PAGE_OBJECT]) + ); + + $this->assertEquals("SSR Test\n", $response->head); + $this->assertEquals('
SSR Response
', $response->body); + } + + public function test_it_uses_the_vite_asset_when_it_is_running_hot_even_if_a_bundle_file_is_present() + { + config([ + 'inertia.ssr.enabled' => true, + 'inertia.ssr.bundle' => __DIR__.'/Stubs/bundle.js', + ]); + + Vite::shouldReceive('isRunningHot')->andReturn(true); + Vite::shouldReceive('asset')->with('render')->andReturn($viteUrl = 'http://localhost:3000/some-url'); + + Http::fake([ + $viteUrl => Http::response(json_encode([ + 'head' => ['SSR Test', ''], + 'body' => '
SSR Response
', + ])), + ]); + + $this->assertNotNull( + $response = $this->gateway->dispatch(['page' => self::EXAMPLE_PAGE_OBJECT]) + ); + + $this->assertEquals("SSR Test\n", $response->head); + $this->assertEquals('
SSR Response
', $response->body); + } + + public function test_it_returns_null_when_the_http_request_fails() + { + config([ + 'inertia.ssr.enabled' => true, + 'inertia.ssr.bundle' => __DIR__.'/Stubs/bundle.js', + ]); + + Vite::shouldReceive('isRunningHot')->andReturn(false); + + Http::fake([ + $this->gateway->getHttpUrl() => Http::response(null, 500), + ]); + + $this->assertNull($this->gateway->dispatch(['page' => self::EXAMPLE_PAGE_OBJECT])); + } + + public function test_it_returns_null_when_invalid_json_is_returned() + { + config([ + 'inertia.ssr.enabled' => true, + 'inertia.ssr.bundle' => __DIR__.'/Stubs/bundle.js', + ]); + + Vite::shouldReceive('isRunningHot')->andReturn(false); + + Http::fake([ + $this->gateway->getHttpUrl() => Http::response('invalid json'), + ]); + + $this->assertNull($this->gateway->dispatch(['page' => self::EXAMPLE_PAGE_OBJECT])); + } +} diff --git a/tests/Stubs/bundle.js b/tests/Stubs/bundle.js new file mode 100644 index 00000000..940a3ff0 --- /dev/null +++ b/tests/Stubs/bundle.js @@ -0,0 +1 @@ +console.log("Hello world!"); diff --git a/tests/TestCase.php b/tests/TestCase.php index 9e2a8833..a47918b1 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -10,6 +10,8 @@ abstract class TestCase extends Orchestra { + protected const EXAMPLE_PAGE_OBJECT = ['component' => 'Foo/Bar', 'props' => ['foo' => 'bar'], 'url' => '/test', 'version' => '', 'encryptHistory' => false, 'clearHistory' => false]; + protected function getPackageProviders($app): array { return [