MSBuild: Conditionally run webpack only when there's changes - visual-studio

We're using Visual Studio 2019 and have successfully configured MSBuild tasks to run webpack for us - both in Debug and Production mode.
But it is a bit overkill to run everytime the project builds e.g. when we run unit tests and there is no changes to the Web project, we don't need webpack to execute.
Is there a way to configure the build task to only execute when files in a particular folder have changed?
Here is our current configuration:
<Target Name="DebugRunWebpack" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug'">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<!-- In development, the dist files won't exist on the first run or when cloning to
a different machine, so rebuild them if not already present. -->
<Message Importance="high" Text="Performing first-run Webpack build..." />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js" />
<Exec Command="node node_modules/webpack/bin/webpack.js" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec Command="npm install" />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" />
<Exec Command="node node_modules/webpack/bin/webpack.js --env.prod" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="wwwroot\dist\**" />
<ResolvedFileToPublish Include="#(DistFiles->'%(FullPath)')" Exclude="#(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ResolvedFileToPublish>
</ItemGroup>
</Target>

Is there a way to configure the build task to only execute when files
in a particular folder have changed?
You can differentiate the publish and build process by the DeployOnBuild Property.
Solution
You only need to add a condition to DebugRunWebpack target:
<Target Name="DebugRunWebpack" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' and 'DeployOnBuild'=='true'">
<!-- Ensure Node.js is installed -->
......
</Target>
And it will only execute the target during publish process.
In addition, PublishRunWebpack target runs after ComputeFilesToPublish which belongs to Publish process. So you do not worry about it.
Update 1
To detect whether these files changed, you can try this condition:
<Target Name="xxxx" Condition="$([System.DateTime]::Parse('%(ModifiedTime)').Ticks) > $([System.IO.File]::GetLastWriteTime('$(xxxxxxxx)%(RecursiveDir)%(Filename)%(Extension)').Ticks)" />
Note $(xxxxxxxx)%(RecursiveDir)%(Filename)%(Extension) means all the files in such folder
Besides, you can refer to this document.
Hope it could help you.

Related

Building web project causes web config to be updated setting 'environmentVariable' to 'Development'

Time back I created a .net core with react SPA project from a template, ever since I have been unable to identify what is causing the web.config to be update each time I build.
Well, to be precise it is updated only adding back the environment variables tags, setting the environment to Development.
Can anyone please tell me how to prevent this?
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
</environmentVariables>
Here is the csproj file content:
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OData" Version="8.0.12" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.13" />
<PackageReference Include="Minded" Version="0.1.3" />
[...]
<ItemGroup>
<!-- Don't publish the SPA source files, but do show them in the project files list -->
<Content Remove="$(SpaRoot)**" />
<None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
<ItemGroup>
<Folder Include="ClientApp\src\components\" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)build\**" />
<ResolvedFileToPublish Include="#(DistFiles->'%(FullPath)')" Exclude="#(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>

Is there any way to publish different profiles of React based on the environment parameter in Dotnet 6

As you can see, on dotnet publish command, it automatically assumes that it is being published only in production, but suppose that I want to publish for staging environment, is there any way to configure the target so that when executed for instance: dotnet publish --environment Staging it should run npm run build --staging
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install --force" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build --prod" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr -- --prod" Condition=" '$(BuildServerSideRenderer)' == 'true' " />
<ItemGroup>
<DistFiles Include="$(SpaRoot)build\**" />
<DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
<ResolvedFileToPublish Include="#(DistFiles->'%(FullPath)')" Exclude="#(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ResolvedFileToPublish>
</ItemGroup>
</Target>

msbuild target - AfterTargets="Build" for the solution?

I am looking for a way to run my defined Target only once per build process and not for every project that gets build.
I have defined the following in Directory.Build.props
<Target Name="MyTarget" AfterTargets="AfterBuild" >
<Message Text="Hello World!!!" Importance="High" />
</Target>
Should only run once no matter how many projects the msbuild process is building, currently it happens for each project.
It shouldn't matter if I hit (Re-)Build Soltution or (Re-)Build [ProjectName] or hit F5 in Visual Studio, as long any build happens I want to exectue MyTarget only once.
Just answer this situation:
If my guess is right, pure msbuild function is not enough and you have to use a external file to help it work.
create a file called test.txt on the solution folder and write 0 in the txt file.
Then, modify your Directory.Build.props like this:
<Project>
<PropertyGroup>
<Record></Record>
</PropertyGroup>
<ItemGroup>
<File Include="..\test.txt"></File>
</ItemGroup>
<Target Name="GetConditionValue" BeforeTargets="PrepareForBuild">
<ReadLinesFromFile File="#(File)">
<Output TaskParameter="Lines" PropertyName="Record"/>
</ReadLinesFromFile>
</Target>
<Target Name="MyTarget" AfterTargets="Build" Condition="'$(Record)'=='0'">
<WriteLinesToFile File="#(File)" Lines="2" Overwrite="true"></WriteLinesToFile>
<Message Text="Hello World!!!" Importance="High" />
</Target>
</Project>
When you start a new build process, you should clear the test.txt file to 0 to make a new start.

How to make Visual Studio 2017 call the `yarn start`

I have a React project, and I run it with yarn start that runs react-scripts start.
I want to use Visual Studio 2017 to edit code and run the app, but I don't want to run using the VS2017 node.js things, I want to hit F5 and continue using the react-scripts.
Is there some way to do that?
## Update ##
For those who want a better .njsproj file format, there is this idea in the developer community that deserve an upvote: https://developercommunity.visualstudio.com/content/idea/351736/use-new-project-format-for-njsproj-files.html
Another workaround might be changing your script section on package.json to
"scripts": {
"start": "yarn react-scripts start"
...
}
which will be reached by a configuration on Startup.cs.
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
You could add it as a pre-build step in the project settings, but I think a better way is with custom targets in your .csproj file. I'm using vue instead of react. This is what I use in my .csproj, and you could do something similar.
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="RunNpmBuild" Condition="'$(Configuration)' != 'ServerOnly'">
<Exec Command="yarn" WorkingDirectory="./www" />
<Exec Command="npm run build" WorkingDirectory="./www" />
</Target>
<Target Name="BeforeBuild" DependsOnTargets="RunNpmBuild">
</Target>
Note that I also have an additional build configuration called "ServerOnly" based on the debug configuration so that I can f5 debug just the server without having to run yarn or my npm build.
It's the same thing for react and all other Single Page Application projects.
I've an SPA written in react and I used this in csproj file
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'yarn'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="yarn install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="yarn install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="yarn build" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)build\**; $(SpaRoot)build-ssr\**" />
<ResolvedFileToPublish Include="#(DistFiles->'%(FullPath)')" Exclude="#(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
Another way is by using Yarn.MSBuild. It is explained how in this answer.

Visual Studio. Publish project from command line

Is there a way to publish a web project in MS Visual Studio 2010 using CLI? I use DevEnv.exe /Build to build a project and it works fine, but I could not find option to Publish a project.
One other thing I want to mention. I am trying to publish web project NOT to the IIS directly. I have a location where I publish several projects and then build them automatically into NSIS bundle to be deployed.
From ASP.NET Web Deployment using Visual Studio: Command Line Deployment, you can use
msbuild myproject.csproj /p:DeployOnBuild=true /p:PublishProfile=MyPublishProfile
where MyPublishProfile is the profile name that you've already set up somewhere
What works best is to add following target to the project file:
<Target Name="AfterBuild">
<Message Text="Copying to Deployment Dir:" />
<Copy SourceFiles="#(Content)" DestinationFolder="..\XXX\%(Content.RelativeDir)" />
<CreateItem Include="$(OutputPath)\*">
<Output TaskParameter="Include" ItemName="Binaries"/>
</CreateItem>
<Copy SourceFiles="#(Binaries)" DestinationFolder="..\XXX\bin" />
</Target>
This way, whenever project got build (from command line or from IDE) it automatically get deployed to specified folder. Thank you everybody for pointing me to right direction.
The /t:publish switch is for ClickOnce applications only, it's not applicable to web projects. Hence the error saying it's unpublishable. :)
#RobKent As https://stackoverflow.com/a/2775437/21233364
using
<Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release' ">
<Message Text="Copying to Deployment Dir:" />
<Copy SourceFiles="#(Content)" DestinationFolder="..\XXX\%(Content.RelativeDir)" />
<CreateItem Include="$(OutputPath)\*">
<Output TaskParameter="Include" ItemName="Binaries"/>
</CreateItem>
<Copy SourceFiles="#(Binaries)" DestinationFolder="..\XXX\bin" />
</Target>
you can have publish only on release compiling.

Resources