Skip to content

Commit 2cb3b6d

Browse files
committed
[2.x] Ability to perform a SSR request without a bundle
inertiajs/inertia-laravel#751
1 parent 81aa149 commit 2cb3b6d

File tree

5 files changed

+200
-4
lines changed

5 files changed

+200
-4
lines changed

InertiaCore/Models/InertiaOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ public class InertiaOptions
66

77
public bool SsrEnabled { get; set; } = false;
88
public string SsrUrl { get; set; } = "http://127.0.0.1:13714/render";
9+
public bool SsrDispatchWithoutBundle { get; set; } = false;
910
public bool EncryptHistory { get; set; } = false;
1011
}

InertiaCore/ResponseFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public Response Render(string component, object? props = null)
5959

6060
public async Task<IHtmlContent> Head(dynamic model)
6161
{
62-
if (!_options.Value.SsrEnabled) return new HtmlString("");
62+
if (!_options.Value.SsrEnabled || !_gateway.ShouldDispatch()) return new HtmlString("");
6363

6464
var context = _contextAccessor.HttpContext!;
6565

@@ -74,7 +74,7 @@ public async Task<IHtmlContent> Head(dynamic model)
7474

7575
public async Task<IHtmlContent> Html(dynamic model)
7676
{
77-
if (_options.Value.SsrEnabled)
77+
if (_options.Value.SsrEnabled && _gateway.ShouldDispatch())
7878
{
7979
var context = _contextAccessor.HttpContext!;
8080

InertiaCore/Ssr/Gateway.cs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,26 @@
22
using System.Text;
33
using System.Text.Json;
44
using System.Text.Json.Serialization;
5+
using InertiaCore.Models;
6+
using Microsoft.Extensions.Options;
7+
using Microsoft.AspNetCore.Hosting;
58

69
namespace InertiaCore.Ssr;
710

811
internal interface IGateway
912
{
1013
public Task<SsrResponse?> Dispatch(object model, string url);
14+
public bool ShouldDispatch();
1115
}
1216

1317
internal class Gateway : IGateway
1418
{
1519
private readonly IHttpClientFactory _httpClientFactory;
20+
private readonly IOptions<InertiaOptions> _options;
21+
private readonly IWebHostEnvironment _environment;
1622

17-
public Gateway(IHttpClientFactory httpClientFactory) => _httpClientFactory = httpClientFactory;
23+
public Gateway(IHttpClientFactory httpClientFactory, IOptions<InertiaOptions> options, IWebHostEnvironment environment) =>
24+
(_httpClientFactory, _options, _environment) = (httpClientFactory, options, environment);
1825

1926
public async Task<SsrResponse?> Dispatch(dynamic model, string url)
2027
{
@@ -30,4 +37,47 @@ internal class Gateway : IGateway
3037
var response = await client.PostAsync(url, content);
3138
return await response.Content.ReadFromJsonAsync<SsrResponse>();
3239
}
40+
41+
public bool ShouldDispatch()
42+
{
43+
return ShouldDispatchWithoutBundle() || BundleExists();
44+
}
45+
46+
private bool ShouldDispatchWithoutBundle()
47+
{
48+
return _options.Value.SsrDispatchWithoutBundle;
49+
}
50+
51+
private bool BundleExists()
52+
{
53+
var commonBundlePaths = new[]
54+
{
55+
"~/public/js/ssr.js",
56+
"~/public/build/ssr.js",
57+
"~/wwwroot/js/ssr.js",
58+
"~/wwwroot/build/ssr.js",
59+
"~/dist/ssr.js",
60+
"~/build/ssr.js"
61+
};
62+
63+
foreach (var path in commonBundlePaths)
64+
{
65+
var resolvedPath = ResolvePath(path);
66+
if (!string.IsNullOrEmpty(resolvedPath) && File.Exists(resolvedPath))
67+
{
68+
return true;
69+
}
70+
}
71+
72+
return false;
73+
}
74+
75+
private string? ResolvePath(string path)
76+
{
77+
if (path.StartsWith("~/"))
78+
{
79+
return Path.Combine(_environment.ContentRootPath, path[2..]);
80+
}
81+
return Path.IsPathRooted(path) ? path : Path.Combine(_environment.ContentRootPath, path);
82+
}
3383
}

InertiaCoreTests/Setup.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.AspNetCore.Mvc.Abstractions;
99
using Microsoft.AspNetCore.Routing;
1010
using Microsoft.Extensions.Options;
11+
using Microsoft.AspNetCore.Hosting;
1112
using Moq;
1213

1314
namespace InertiaCoreTests;
@@ -21,11 +22,14 @@ public void Setup()
2122
{
2223
var contextAccessor = new Mock<IHttpContextAccessor>();
2324
var httpClientFactory = new Mock<IHttpClientFactory>();
25+
var environment = new Mock<IWebHostEnvironment>();
26+
environment.SetupGet(x => x.ContentRootPath).Returns(Path.GetTempPath());
2427

25-
var gateway = new Gateway(httpClientFactory.Object);
2628
var options = new Mock<IOptions<InertiaOptions>>();
2729
options.SetupGet(x => x.Value).Returns(new InertiaOptions());
2830

31+
var gateway = new Gateway(httpClientFactory.Object, options.Object, environment.Object);
32+
2933
_factory = new ResponseFactory(contextAccessor.Object, gateway, options.Object);
3034
}
3135

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
using InertiaCore.Models;
2+
using InertiaCore.Ssr;
3+
using Microsoft.AspNetCore.Hosting;
4+
using Microsoft.Extensions.Options;
5+
using Moq;
6+
7+
namespace InertiaCoreTests;
8+
9+
public partial class Tests
10+
{
11+
[Test]
12+
[Description("Test SSR dispatch should not dispatch by default when no bundle exists")]
13+
public void TestSsrDispatchDefaultBehaviorWithoutBundle()
14+
{
15+
var httpClientFactory = new Mock<IHttpClientFactory>();
16+
var environment = new Mock<IWebHostEnvironment>();
17+
environment.SetupGet(x => x.ContentRootPath).Returns(Path.GetTempPath());
18+
19+
var options = new Mock<IOptions<InertiaOptions>>();
20+
options.SetupGet(x => x.Value).Returns(new InertiaOptions { SsrDispatchWithoutBundle = false });
21+
22+
var gateway = new Gateway(httpClientFactory.Object, options.Object, environment.Object);
23+
24+
Assert.That(gateway.ShouldDispatch(), Is.False);
25+
}
26+
27+
[Test]
28+
[Description("Test SSR dispatch should dispatch when SsrDispatchWithoutBundle is enabled")]
29+
public void TestSsrDispatchWithoutBundleEnabled()
30+
{
31+
var httpClientFactory = new Mock<IHttpClientFactory>();
32+
var environment = new Mock<IWebHostEnvironment>();
33+
environment.SetupGet(x => x.ContentRootPath).Returns(Path.GetTempPath());
34+
35+
var options = new Mock<IOptions<InertiaOptions>>();
36+
options.SetupGet(x => x.Value).Returns(new InertiaOptions { SsrDispatchWithoutBundle = true });
37+
38+
var gateway = new Gateway(httpClientFactory.Object, options.Object, environment.Object);
39+
40+
Assert.That(gateway.ShouldDispatch(), Is.True);
41+
}
42+
43+
[Test]
44+
[Description("Test SSR dispatch should dispatch when bundle exists")]
45+
public void TestSsrDispatchWithBundleExists()
46+
{
47+
var tempDir = Path.GetTempPath();
48+
var bundleDir = Path.Combine(tempDir, "wwwroot", "js");
49+
Directory.CreateDirectory(bundleDir);
50+
51+
var bundlePath = Path.Combine(bundleDir, "ssr.js");
52+
File.WriteAllText(bundlePath, "// SSR bundle");
53+
54+
try
55+
{
56+
var httpClientFactory = new Mock<IHttpClientFactory>();
57+
var environment = new Mock<IWebHostEnvironment>();
58+
environment.SetupGet(x => x.ContentRootPath).Returns(tempDir);
59+
60+
var options = new Mock<IOptions<InertiaOptions>>();
61+
options.SetupGet(x => x.Value).Returns(new InertiaOptions { SsrDispatchWithoutBundle = false });
62+
63+
var gateway = new Gateway(httpClientFactory.Object, options.Object, environment.Object);
64+
65+
Assert.That(gateway.ShouldDispatch(), Is.True);
66+
}
67+
finally
68+
{
69+
if (File.Exists(bundlePath))
70+
File.Delete(bundlePath);
71+
if (Directory.Exists(bundleDir))
72+
Directory.Delete(bundleDir, true);
73+
}
74+
}
75+
76+
[Test]
77+
[Description("Test SSR dispatch should dispatch when either bundle exists or dispatch without bundle is enabled")]
78+
public void TestSsrDispatchWithBundleAndDispatchWithoutBundleEnabled()
79+
{
80+
var tempDir = Path.GetTempPath();
81+
var bundleDir = Path.Combine(tempDir, "build");
82+
Directory.CreateDirectory(bundleDir);
83+
84+
var bundlePath = Path.Combine(bundleDir, "ssr.js");
85+
File.WriteAllText(bundlePath, "// SSR bundle");
86+
87+
try
88+
{
89+
var httpClientFactory = new Mock<IHttpClientFactory>();
90+
var environment = new Mock<IWebHostEnvironment>();
91+
environment.SetupGet(x => x.ContentRootPath).Returns(tempDir);
92+
93+
var options = new Mock<IOptions<InertiaOptions>>();
94+
options.SetupGet(x => x.Value).Returns(new InertiaOptions { SsrDispatchWithoutBundle = true });
95+
96+
var gateway = new Gateway(httpClientFactory.Object, options.Object, environment.Object);
97+
98+
Assert.That(gateway.ShouldDispatch(), Is.True);
99+
}
100+
finally
101+
{
102+
if (File.Exists(bundlePath))
103+
File.Delete(bundlePath);
104+
if (Directory.Exists(bundleDir))
105+
Directory.Delete(bundleDir, true);
106+
}
107+
}
108+
109+
[Test]
110+
[Description("Test SSR dispatch checks multiple common bundle paths")]
111+
public void TestSsrDispatchChecksMultipleBundlePaths()
112+
{
113+
var tempDir = Path.GetTempPath();
114+
var bundleDir = Path.Combine(tempDir, "dist");
115+
Directory.CreateDirectory(bundleDir);
116+
117+
var bundlePath = Path.Combine(bundleDir, "ssr.js");
118+
File.WriteAllText(bundlePath, "// SSR bundle in dist");
119+
120+
try
121+
{
122+
var httpClientFactory = new Mock<IHttpClientFactory>();
123+
var environment = new Mock<IWebHostEnvironment>();
124+
environment.SetupGet(x => x.ContentRootPath).Returns(tempDir);
125+
126+
var options = new Mock<IOptions<InertiaOptions>>();
127+
options.SetupGet(x => x.Value).Returns(new InertiaOptions { SsrDispatchWithoutBundle = false });
128+
129+
var gateway = new Gateway(httpClientFactory.Object, options.Object, environment.Object);
130+
131+
Assert.That(gateway.ShouldDispatch(), Is.True);
132+
}
133+
finally
134+
{
135+
if (File.Exists(bundlePath))
136+
File.Delete(bundlePath);
137+
if (Directory.Exists(bundleDir))
138+
Directory.Delete(bundleDir, true);
139+
}
140+
}
141+
}

0 commit comments

Comments
 (0)