Skip to content

Commit 60d1eb4

Browse files
authored
Merge pull request #1 from ucdavis/swe/AddVite
Add support for Vite dev server
2 parents cb6431f + e3dea55 commit 60d1eb4

8 files changed

+184
-18
lines changed

MvcReact.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<PackageReadmeFile>README.md</PackageReadmeFile>
66
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
77
<PackageProjectUrl>https://github.com/ucdavis/MvcReact</PackageProjectUrl>
8-
<Version>1.0.0</Version>
8+
<Version>2.0.0</Version>
99
<Authors>Spruce Weber-Milne</Authors>
1010
<Company>UC Davis</Company>
1111
<Description>Library to simplify setup of AspNetCore app that serves both Mvc and React pages</Description>

README.md

+81-6
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44

55

66
# MvcReact
7-
Library to simplify setup of AspNetCore app that serves both Mvc and React pages
7+
Library to simplify setup of AspNetCore app that serves both Mvc and React pages.
8+
Supported dev servers are CRA (Webpack) and Vite.
89

910
## Installation
1011
```bash
1112
dotnet add package MvcReact
13+
# to install vite dependencies
14+
cd ClientApp
15+
npm install -D vite @vitejs/plugin-react
1216
```
1317

18+
1419
## Usage
1520

1621
Add a using statement in your app initialization code (usually `Program.cs` or `Startup.cs`)
@@ -19,29 +24,53 @@ Add a using statement in your app initialization code (usually `Program.cs` or `
1924
using MvcReact;
2025
```
2126

22-
Initialize services
27+
Initialize CRA services
2328

2429
```csharp
25-
services.AddMvcReact();
30+
services.AddCraServices();
2631
```
2732

2833
Or for explicit control over settings...
2934

3035
```csharp
31-
services.AddMvcReact(options =>
36+
services.AddCraServices(options =>
3237
{
3338
options.SourcePath = "ClientApp";
3439
options.BuildPath = "ClientApp/build";
3540
options.IndexHtmlPath = "ClientApp/build/index.html";
3641
options.StaticAssetBasePath = "/static";
3742
options.StaticAssetHeaderCacheMaxAgeDays = 365;
38-
options.DevServerBundlePath = "/static/js/bundle.js";
43+
options.CraDevServerBundlePath = "/static/js/bundle.js";
3944
options.DevServerStartScript = "start";
45+
options.DevServerPort = 3000;
4046
options.TagHelperCacheMinutes = 30;
4147
options.ExcludeHmrPathsRegex = "^(?!ws|.*?hot-update.js(on)?).*$";
4248
});
4349
```
4450

51+
Initialize Vite services
52+
53+
```csharp
54+
services.AddViteServices();
55+
```
56+
57+
Or for explicit control over settings...
58+
59+
```csharp
60+
services.AddViteServices(options =>
61+
{
62+
options.SourcePath = "ClientApp";
63+
options.BuildPath = "ClientApp/build";
64+
options.IndexHtmlPath = "ClientApp/build/index.html";
65+
options.StaticAssetBasePath = "/static";
66+
options.StaticAssetHeaderCacheMaxAgeDays = 365;
67+
options.DevServerStartScript = "start";
68+
options.DevServerPort = 5173;
69+
options.ViteDevServerEntry = "/index.tsx";
70+
options.TagHelperCacheMinutes = 30;
71+
});
72+
```
73+
4574
`UseMvcReactStaticFiles` and `UseMvcReact` are the important extension methods here, along with `MvcReactOptions.ExcludeHmrPathsRegex` for letting hmr requests fall through
4675

4776
```csharp
@@ -100,4 +129,50 @@ Use `<react-scripts />` and `<react-styles />` tag helpers to embed relavent tag
100129
{
101130
<react-scripts />
102131
}
103-
```
132+
```
133+
134+
Configuring Vite
135+
136+
```typescript
137+
import { UserConfig, defineConfig, loadEnv } from "vite";
138+
import react from "@vitejs/plugin-react";
139+
140+
export default defineConfig(async ({ mode }) => {
141+
// Load app-level env vars to node-level env vars.
142+
const env = { ...process.env, ...loadEnv(mode, process.cwd()) };
143+
process.env = env;
144+
145+
const config: UserConfig = {
146+
root: "src",
147+
publicDir: "public",
148+
build: {
149+
outDir: "build",
150+
// rollupOptions beyond scope of this snippet
151+
},
152+
plugins: [react()],
153+
optimizeDeps: {
154+
include: [],
155+
},
156+
server: {
157+
port: 5173,
158+
hmr: {
159+
clientPort: 5173,
160+
},
161+
strictPort: true,
162+
},
163+
};
164+
165+
return config;
166+
});
167+
```
168+
169+
Vite tends to leave orphaned node processes after a debugging session. This can be addressed by
170+
having the npm start script kill the node process listening on a given port.
171+
172+
```json
173+
{
174+
"scripts": {
175+
"start": "npx kill-port 5173 && vite"
176+
}
177+
}
178+
```

azure-pipelines.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ variables:
88
solution: "**/*.sln"
99
buildPlatform: "Any CPU"
1010
buildConfiguration: "Release"
11-
majorVersion: 1
11+
majorVersion: 2
1212
minorVersion: 0
1313
patchVersion: $[counter(variables['minorVersion'], 0)]
1414
version: $(majorVersion).$(minorVersion).$(patchVersion)

src/ApplicationBuilderExtensions.cs

+4
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ public static class ApplicationBuilderExtensions
1212
{
1313
public static IApplicationBuilder UseMvcReact(this IApplicationBuilder app)
1414
{
15+
// MS suggested hack to ensure correct ordering of routing and spa middleware
16+
app.UseEndpoints(e => { });
17+
1518
var env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
1619
var options = app.ApplicationServices.GetRequiredService<IOptions<MvcReactOptions>>().Value;
1720

1821
app.UseSpa(spa =>
1922
{
2023
spa.Options.SourcePath = options.SourcePath;
24+
spa.Options.DevServerPort = options.DevServerPort;
2125

2226
if (env.IsDevelopment())
2327
{

src/DevServerType.cs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace MvcReact;
2+
3+
public static class DevServerType
4+
{
5+
public const string Vite = "Vite";
6+
public const string CRA = "CRA";
7+
}

src/MvcReactOptions.cs

+17-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,23 @@ public class MvcReactOptions
1212
/// The path to the js bundle served by the CRA dev server
1313
/// </summary>
1414
/// <value></value>
15-
public string DevServerBundlePath { get; set; } = "";
15+
public string CraDevServerBundlePath { get; set; } = "";
16+
17+
/// <summary>
18+
/// The path to the entry point for the Vite dev server
19+
/// </summary>
20+
public string ViteDevServerEntry { get; set; } = "main.tsx";
21+
22+
/// <summary>
23+
/// The type of dev server used (CRA or Vite)
24+
/// </summary>
25+
public string DevServerType { get; set; } = MvcReact.DevServerType.CRA;
26+
27+
/// <summary>
28+
/// The path to the js bundle served by the CRA dev server
29+
/// </summary>
30+
/// <value></value>
31+
public int DevServerPort { get; set; } = 3000;
1632

1733
/// <summary>
1834
/// The npm script to start the CRA dev server

src/ServiceCollectionExtensions.cs

+45-5
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ namespace MvcReact;
99

1010
public static class ServiceCollectionExtensions
1111
{
12-
public static IServiceCollection AddMvcReact(this IServiceCollection services)
12+
public static IServiceCollection AddCraServices(this IServiceCollection services)
1313
{
14-
return services.AddMvcReact(_ => { });
14+
return services.AddCraServices(_ => { });
1515
}
1616

17-
public static IServiceCollection AddMvcReact(this IServiceCollection services, Action<MvcReactOptions> configureOptions)
17+
public static IServiceCollection AddCraServices(this IServiceCollection services, Action<MvcReactOptions> configureOptions)
1818
{
1919
Action<MvcReactOptions> optionBuilder = options =>
2020
{
@@ -24,14 +24,55 @@ public static IServiceCollection AddMvcReact(this IServiceCollection services, A
2424
options.IndexHtmlPath = "ClientApp/build/index.html";
2525
options.StaticAssetBasePath = "/static";
2626
options.StaticAssetHeaderCacheMaxAgeDays = 365;
27-
options.DevServerBundlePath = "/static/js/bundle.js";
27+
options.CraDevServerBundlePath = "/static/js/bundle.js";
2828
options.DevServerStartScript = "start";
29+
options.DevServerType = DevServerType.CRA;
30+
options.DevServerPort = 3000;
2931
options.TagHelperCacheMinutes = 30;
3032
options.ExcludeHmrPathsRegex = "^(?!ws|.*?hot-update.js(on)?).*$";
3133

3234
// allow for custom config...
3335
configureOptions(options);
3436
};
37+
38+
AddReactMvcServices(services, optionBuilder);
39+
40+
return services;
41+
}
42+
43+
public static IServiceCollection AddViteServices(this IServiceCollection services)
44+
{
45+
return services.AddViteServices(_ => { });
46+
}
47+
48+
public static IServiceCollection AddViteServices(this IServiceCollection services, Action<MvcReactOptions> configureOptions)
49+
{
50+
Action<MvcReactOptions> optionBuilder = options =>
51+
{
52+
// default config happens here
53+
options.SourcePath = "ClientApp";
54+
options.BuildPath = "ClientApp/build";
55+
options.IndexHtmlPath = "ClientApp/build/index.html";
56+
options.StaticAssetBasePath = "/static";
57+
options.StaticAssetHeaderCacheMaxAgeDays = 365;
58+
options.DevServerStartScript = "start";
59+
options.DevServerType = DevServerType.Vite;
60+
options.DevServerPort = 5173;
61+
options.ViteDevServerEntry = "/index.tsx";
62+
options.TagHelperCacheMinutes = 30;
63+
64+
65+
// allow for custom config...
66+
configureOptions(options);
67+
};
68+
69+
AddReactMvcServices(services, optionBuilder);
70+
71+
return services;
72+
}
73+
74+
private static void AddReactMvcServices(IServiceCollection services, Action<MvcReactOptions> optionBuilder)
75+
{
3576
services.Configure(optionBuilder);
3677

3778
// not sure if there is a better way to get at the options here
@@ -46,7 +87,6 @@ public static IServiceCollection AddMvcReact(this IServiceCollection services, A
4687
services.AddScoped<IInternalFileProvider>(_ => new InternalFileProvider(Directory.GetCurrentDirectory())); // lgtm [cs/local-not-disposed]
4788
services.AddTransient<ITagHelper, ReactScriptsTagHelper>();
4889
services.AddTransient<ITagHelper, ReactStylesTagHelper>();
49-
return services;
5090
}
5191

5292
}

src/TagHelpers/ReactScriptsTagHelper.cs

+28-4
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,34 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
3434

3535
if (string.Equals(environment, Constants.ENVIRONMENT_DEVELOPMENT, StringComparison.OrdinalIgnoreCase))
3636
{
37-
// in development, we want to use the CRA dev server and not cache anything
38-
output.TagName = "script";
39-
output.Attributes.Add("src", _options.DevServerBundlePath);
40-
output.TagMode = TagMode.StartTagAndEndTag;
37+
// in development, we want to use the dev server and not cache anything
38+
switch (_options.DevServerType)
39+
{
40+
case DevServerType.CRA:
41+
output.TagName = "script";
42+
output.Attributes.Add("src", _options.CraDevServerBundlePath);
43+
output.TagMode = TagMode.StartTagAndEndTag;
44+
break;
45+
case DevServerType.Vite:
46+
// set TagName null in order to allow explicitly set content
47+
output.TagName = null;
48+
// Vite doesn't have an opportunity to inject @react-refresh init code when it's not serving index.html,
49+
// so we have to do it manually
50+
output.Content.SetHtmlContent($@"
51+
<script type=""module"">
52+
import RefreshRuntime from ""http://localhost:{_options.DevServerPort}/@react-refresh""
53+
RefreshRuntime.injectIntoGlobalHook(window)
54+
window.$RefreshReg$ = () => {{}}
55+
window.$RefreshSig$ = () => (type) => type
56+
window.__vite_plugin_react_preamble_installed__ = true
57+
</script>
58+
<script type=""module"" src=""http://localhost:{_options.DevServerPort}/@vite/client""></script>
59+
<script type=""module"" src=""http://localhost:{_options.DevServerPort}{_options.ViteDevServerEntry}""></script>
60+
");
61+
break;
62+
default:
63+
throw new Exception($"Unknown dev server type: {_options.DevServerType}");
64+
}
4165
return;
4266
}
4367

0 commit comments

Comments
 (0)