-
Notifications
You must be signed in to change notification settings - Fork 717
Add ViteApp and Npm Package Manager #12283
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
Changes from 7 commits
fb48603
7fe9800
167c5bf
87288a4
5cdddfd
083ef66
95e9dc2
f4528a6
6517db1
dcf6b33
7965516
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,12 +27,16 @@ | |
| } | ||
| }, | ||
| "angular": { | ||
| "type": "dockerfile.v0", | ||
| "path": "../AspireJavaScript.Angular/Dockerfile", | ||
| "context": "../AspireJavaScript.Angular", | ||
| "type": "container.v1", | ||
| "build": { | ||
| "context": "../AspireJavaScript.Angular", | ||
| "dockerfile": "../AspireJavaScript.Angular/Dockerfile" | ||
| }, | ||
| "env": { | ||
| "NODE_ENV": "development", | ||
| "WEATHERAPI_HTTP": "{weatherapi.bindings.http.url}", | ||
| "services__weatherapi__http__0": "{weatherapi.bindings.http.url}", | ||
| "WEATHERAPI_HTTPS": "{weatherapi.bindings.https.url}", | ||
| "services__weatherapi__https__0": "{weatherapi.bindings.https.url}", | ||
| "PORT": "{angular.bindings.http.targetPort}" | ||
| }, | ||
|
|
@@ -47,12 +51,16 @@ | |
| } | ||
| }, | ||
| "react": { | ||
| "type": "dockerfile.v0", | ||
| "path": "../AspireJavaScript.React/Dockerfile", | ||
| "context": "../AspireJavaScript.React", | ||
| "type": "container.v1", | ||
| "build": { | ||
| "context": "../AspireJavaScript.React", | ||
| "dockerfile": "../AspireJavaScript.React/Dockerfile" | ||
| }, | ||
| "env": { | ||
| "NODE_ENV": "development", | ||
| "WEATHERAPI_HTTP": "{weatherapi.bindings.http.url}", | ||
| "services__weatherapi__http__0": "{weatherapi.bindings.http.url}", | ||
| "WEATHERAPI_HTTPS": "{weatherapi.bindings.https.url}", | ||
| "services__weatherapi__https__0": "{weatherapi.bindings.https.url}", | ||
| "BROWSER": "none", | ||
| "PORT": "{react.bindings.http.targetPort}" | ||
|
|
@@ -68,12 +76,16 @@ | |
| } | ||
| }, | ||
| "vue": { | ||
| "type": "dockerfile.v0", | ||
| "path": "../AspireJavaScript.Vue/Dockerfile", | ||
| "context": "../AspireJavaScript.Vue", | ||
| "type": "container.v1", | ||
| "build": { | ||
| "context": "../AspireJavaScript.Vue", | ||
| "dockerfile": "../AspireJavaScript.Vue/Dockerfile" | ||
| }, | ||
| "env": { | ||
| "NODE_ENV": "development", | ||
| "WEATHERAPI_HTTP": "{weatherapi.bindings.http.url}", | ||
| "services__weatherapi__http__0": "{weatherapi.bindings.http.url}", | ||
| "WEATHERAPI_HTTPS": "{weatherapi.bindings.https.url}", | ||
| "services__weatherapi__https__0": "{weatherapi.bindings.https.url}", | ||
| "PORT": "{vue.bindings.http.targetPort}" | ||
| }, | ||
|
|
@@ -86,6 +98,31 @@ | |
| "external": true | ||
| } | ||
| } | ||
| }, | ||
| "reactvite": { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vhvb1989 this is what wil break azd, we're not deploying this resource but need to build it. |
||
| "type": "container.v1", | ||
| "build": { | ||
| "context": "../AspireJavaScript.Vite", | ||
| "dockerfile": "reactvite.Dockerfile" | ||
| }, | ||
| "env": { | ||
| "NODE_ENV": "development", | ||
| "PORT": "{reactvite.bindings.http.targetPort}", | ||
| "WEATHERAPI_HTTP": "{weatherapi.bindings.http.url}", | ||
| "services__weatherapi__http__0": "{weatherapi.bindings.http.url}", | ||
| "WEATHERAPI_HTTPS": "{weatherapi.bindings.https.url}", | ||
| "services__weatherapi__https__0": "{weatherapi.bindings.https.url}", | ||
| "BROWSER": "none" | ||
| }, | ||
| "bindings": { | ||
| "http": { | ||
| "scheme": "http", | ||
| "protocol": "tcp", | ||
| "transport": "http", | ||
| "targetPort": 8003, | ||
| "external": true | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| FROM node:22-slim | ||
| WORKDIR /app | ||
| COPY . . | ||
| RUN npm install | ||
| RUN npm run build |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Aspire.Hosting.ApplicationModel; | ||
|
|
||
| namespace Aspire.Hosting.NodeJs; | ||
|
|
||
| /// <summary> | ||
| /// Represents an annotation for a JavaScript installer resource. | ||
| /// </summary> | ||
| public sealed class JavaScriptPackageInstallerAnnotation(ExecutableResource installerResource) : IResourceAnnotation | ||
| { | ||
| /// <summary> | ||
| /// The instance of the Installer resource used. | ||
| /// </summary> | ||
| public ExecutableResource Resource { get; } = installerResource; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Aspire.Hosting.ApplicationModel; | ||
|
|
||
| namespace Aspire.Hosting.NodeJs; | ||
|
|
||
| /// <summary> | ||
| /// Represents the annotation for the JavaScript package manager used in a resource. | ||
| /// </summary> | ||
| /// <param name="packageManager">The name of the JavaScript package manager.</param> | ||
| public sealed class JavaScriptPackageManagerAnnotation(string packageManager) : IResourceAnnotation | ||
| { | ||
| /// <summary> | ||
| /// Gets the name of the JavaScript package manager. | ||
| /// </summary> | ||
| public string PackageManager { get; } = packageManager; | ||
|
|
||
| /// <summary> | ||
| /// Gets the command line arguments for the JavaScript package manager's install command. | ||
| /// </summary> | ||
| public string[] InstallCommandLineArgs { get; init; } = []; | ||
|
|
||
| /// <summary> | ||
| /// Gets the command line arguments for the JavaScript package manager's run command. | ||
| /// </summary> | ||
| public string[] RunCommandLineArgs { get; init; } = []; | ||
|
|
||
| /// <summary> | ||
| /// Gets a string value that separates the package manager command line args from the tool's command line args. | ||
| /// By default, this is "--". | ||
| /// </summary> | ||
| public string? CommandSeparator { get; init; } = "--"; | ||
|
|
||
| /// <summary> | ||
| /// Gets the command line arguments for the JavaScript package manager's command that produces assets for distribution. | ||
| /// </summary> | ||
| public string[] BuildCommandLineArgs { get; init; } = []; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,10 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| #pragma warning disable ASPIREDOCKERFILEBUILDER001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. | ||
|
|
||
| using Aspire.Hosting.ApplicationModel; | ||
| using Aspire.Hosting.NodeJs; | ||
| using Aspire.Hosting.Utils; | ||
| using Microsoft.Extensions.Hosting; | ||
|
|
||
|
|
@@ -69,7 +73,7 @@ public static IResourceBuilder<NodeAppResource> AddNpmApp(this IDistributedAppli | |
| .WithIconName("CodeJsRectangle"); | ||
| } | ||
|
|
||
| private static IResourceBuilder<NodeAppResource> WithNodeDefaults(this IResourceBuilder<NodeAppResource> builder) => | ||
| private static IResourceBuilder<TResource> WithNodeDefaults<TResource>(this IResourceBuilder<TResource> builder) where TResource : NodeAppResource => | ||
| builder.WithOtlpExporter() | ||
| .WithEnvironment("NODE_ENV", builder.ApplicationBuilder.Environment.IsDevelopment() ? "development" : "production") | ||
| .WithExecutableCertificateTrustCallback((ctx) => | ||
|
|
@@ -86,4 +90,131 @@ private static IResourceBuilder<NodeAppResource> WithNodeDefaults(this IResource | |
|
|
||
| return Task.CompletedTask; | ||
| }); | ||
|
|
||
| /// <summary> | ||
| /// Adds a Vite app to the distributed application builder. | ||
| /// </summary> | ||
| /// <param name="builder">The <see cref="IDistributedApplicationBuilder"/> to add the resource to.</param> | ||
| /// <param name="name">The name of the Vite app.</param> | ||
| /// <param name="workingDirectory">The working directory of the Vite app.</param> | ||
| /// <param name="useHttps">When true use HTTPS for the endpoints, otherwise use HTTP.</param> | ||
| /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns> | ||
| /// <remarks> | ||
| /// <example> | ||
| /// The following example creates a Vite app using npm as the package manager. | ||
| /// <code lang="csharp"> | ||
| /// var builder = DistributedApplication.CreateBuilder(args); | ||
| /// | ||
| /// builder.AddViteApp("frontend", "./frontend") | ||
| /// .WithNpmPackageManager(); | ||
| /// | ||
| /// builder.Build().Run(); | ||
| /// </code> | ||
| /// </example> | ||
| /// </remarks> | ||
| public static IResourceBuilder<ViteAppResource> AddViteApp(this IDistributedApplicationBuilder builder, [ResourceName] string name, string workingDirectory, bool useHttps = false) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(builder); | ||
| ArgumentException.ThrowIfNullOrEmpty(name); | ||
| ArgumentException.ThrowIfNullOrEmpty(workingDirectory); | ||
|
|
||
| workingDirectory = PathNormalizer.NormalizePathForCurrentPlatform(Path.Combine(builder.AppHostDirectory, workingDirectory)); | ||
| var resource = new ViteAppResource(name, "node", workingDirectory); | ||
|
|
||
| var resourceBuilder = builder.AddResource(resource) | ||
| .WithNodeDefaults() | ||
| .WithIconName("CodeJsRectangle") | ||
| .WithArgs(c => | ||
| { | ||
| if (resource.TryGetLastAnnotation<JavaScriptPackageManagerAnnotation>(out var packageManagerAnnotation)) | ||
| { | ||
| foreach (var arg in packageManagerAnnotation.RunCommandLineArgs) | ||
| { | ||
| c.Args.Add(arg); | ||
| } | ||
| } | ||
| c.Args.Add("dev"); | ||
|
|
||
| if (packageManagerAnnotation?.CommandSeparator is string separator) | ||
| { | ||
| c.Args.Add(separator); | ||
| } | ||
|
|
||
| var targetEndpoint = resource.GetEndpoint("https"); | ||
| if (!targetEndpoint.Exists) | ||
| { | ||
| targetEndpoint = resource.GetEndpoint("http"); | ||
| } | ||
|
|
||
| c.Args.Add("--port"); | ||
| c.Args.Add(targetEndpoint.Property(EndpointProperty.TargetPort)); | ||
| }); | ||
|
|
||
| _ = useHttps | ||
| ? resourceBuilder.WithHttpsEndpoint(env: "PORT") | ||
| : resourceBuilder.WithHttpEndpoint(env: "PORT"); | ||
|
|
||
| return resourceBuilder | ||
| .PublishAsDockerFile(c => | ||
| { | ||
eerhardt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| c.WithDockerfileBuilder(workingDirectory, dockerfileContext => | ||
eerhardt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| if (c.Resource.TryGetLastAnnotation<JavaScriptPackageManagerAnnotation>(out var packageManagerAnnotation) | ||
eerhardt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| && packageManagerAnnotation.BuildCommandLineArgs is { Length: > 0 }) | ||
| { | ||
| var dockerBuilder = dockerfileContext.Builder | ||
| .From("node:22-slim") | ||
| .WorkDir("/app") | ||
| .Copy(".", "."); | ||
|
|
||
| if (packageManagerAnnotation.InstallCommandLineArgs is { Length: > 0 }) | ||
| { | ||
| dockerBuilder | ||
| .Run($"{resourceBuilder.Resource.Command} {string.Join(' ', packageManagerAnnotation.InstallCommandLineArgs)}"); | ||
| } | ||
| dockerBuilder | ||
| .Run($"{resourceBuilder.Resource.Command} {string.Join(' ', packageManagerAnnotation.BuildCommandLineArgs)}"); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Ensures the Node.js packages are installed before the application starts using npm as the package manager. | ||
| /// </summary> | ||
| /// <param name="resource">The NodeAppResource.</param> | ||
| /// <param name="useCI">When true, use <code>npm ci</code>, otherwise use <code>npm install</code> when installing packages.</param> | ||
| /// <param name="configureInstaller">Configure the npm installer resource.</param> | ||
| /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns> | ||
| public static IResourceBuilder<TResource> WithNpmPackageManager<TResource>(this IResourceBuilder<TResource> resource, bool useCI = false, Action<IResourceBuilder<NpmInstallerResource>>? configureInstaller = null) where TResource : NodeAppResource | ||
|
||
| { | ||
| resource.WithCommand("npm"); | ||
| resource.WithAnnotation(new JavaScriptPackageManagerAnnotation("npm") | ||
| { | ||
| InstallCommandLineArgs = [useCI ? "ci" : "install"], | ||
| RunCommandLineArgs = ["run"], | ||
| BuildCommandLineArgs = ["run", "build"] | ||
| }); | ||
|
|
||
| // Only install packages during development, not in publish mode | ||
| if (!resource.ApplicationBuilder.ExecutionContext.IsPublishMode) | ||
| { | ||
| var installerName = $"{resource.Resource.Name}-npm-install"; | ||
| var installer = new NpmInstallerResource(installerName, resource.Resource.WorkingDirectory); | ||
eerhardt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| var installerBuilder = resource.ApplicationBuilder.AddResource(installer) | ||
| .WithArgs([useCI ? "ci" : "install"]) | ||
| .WithParentRelationship(resource.Resource) | ||
| .ExcludeFromManifest(); | ||
|
|
||
| // Make the parent resource wait for the installer to complete | ||
| resource.WaitForCompletion(installerBuilder); | ||
|
|
||
| configureInstaller?.Invoke(installerBuilder); | ||
|
|
||
| resource.WithAnnotation(new JavaScriptPackageInstallerAnnotation(installer)); | ||
| } | ||
|
|
||
| return resource; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.