The Microsoft AJAX Minifier provides a build task which can be used in TFS or local build definitions.
I have succsfully used this in both a local project file (building to seperate files) and in TFS build definitions (overwriting the existing JS files).
I'd like to move to using Visual Studio 2010's 1-click publish feature rather than a TFS build definition, however adding the minification task as an AfterBuild target in the project file doesn't appear to effect the 1-click publish feature.
Using information found in this thread and these articles, I tried creating a file named '[ProjectName].wpp.targets in my WAP root directory, and used the following XML:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\Microsoft\MicrosoftAjax\ajaxmin.tasks" />
<Target Name="Minify" BeforeTargets="MSDeployPublish">
<ItemGroup>
<JS Include="**\*.js" Exclude="**\*.min.js;**\*vsddoc.js;**\*debug.js" />
</ItemGroup>
<ItemGroup>
<CSS Include="**\*.css" Exclude="**\*.min.css" />
</ItemGroup>
<AjaxMin JsSourceFiles="#(JS)" JsSourceExtensionPattern="\.js$" JsTargetExtension=".min.js" CssSourceFiles="#(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".min.css" />
</Target>
</Project>
This doesn't appear to have any effect, and unfortunately Visual Studio doesn't give much in the way of feedback or debugging info for these tools.
Has anyone had any success minifying JavaScript / CSS using the Visual Studio 2010 1-click publish feature?
I just wrote a detailed blog entry on how to do this at
http://sedodream.com/2011/02/25/HowToCompressCSSJavaScriptBeforePublishpackage.aspx and http://blogs.msdn.com/b/webdevtools/archive/2011/02/24/how-to-compress-css-javascript-before-publish-package.aspx.
Here are the contents
Today I saw a post on stackoverflow.com asking Using Microsoft AJAX Minifier with Visual Studio 2010 1-click publish. This is a response to that question. The Web Publishing Pipeline is pretty extensive so it is easy for us to hook in to it in order to perform operation such as these. One of those extension points, as we’ve blogged about before, is creating a .wpp.targets file. If you create a file in the same directory of your project with the name {ProjectName}.wpp.targets then that file will automatically be imported and included in the build/publish process. This makes it easy to edit your build/publish process without always having to edit the project file itself. I will use this technique to demonstrate how to compress the CSS & JavaScript files a project contains before it is published/packaged.
Eventhough the question specifically states Microsoft AJAX Minifier I decided to use the compressor contained in Packer.NET (link in resources section). I did this because when I looked at the MSBuild task for the AJAX Minifier it didn’t look like I could control the output location of the compressed files. Instead it would simply write to the same folder with an extension like .min.cs or .min.js. In any case, when you publish/package your Web Application Project (WAP) the files are copied to a temporary location before the publish/package occurs. The default value for this location is obj{Configuration}\Package\PackageTmp\ where {Configuration} is the build configuration that you are currently using for your WAP. So what we need to do is to allow the WPP to copy all the files to that location and then after that we can compress the CSS and JavaScript that goes in that folder. The target which copies the files to that location is CopyAllFilesToSingleFolderForPackage. (To learn more about these targets take a look at the file %Program Files (x86)%\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets.) To make our target run after this target we can use the MSBuild AfterTargets attribute. The project that I created to demonstrate this is called CompressBeforePublish, because of that I create a new file named CompressBeforePublish.wpp.targets to contain my changes.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="SmallSharpTools.Packer.MSBuild.Packer"
AssemblyFile="$(MSBuildThisFileDirectory)..\Contrib\SmallSharpTools.Packer\SmallSharpTools.Packer.dll" />
<!-- This target will run after the files are copied to PackageTmp folder -->
<Target Name="CompressJsAndCss" AfterTargets="CopyAllFilesToSingleFolderForPackage">
<!-- Discover files to compress -->
<ItemGroup>
<_JavaScriptFiles Include="$(_PackageTempDir)\Scripts\**\*.js" />
<_CssFiles Include="$(_PackageTempDir)\Content\**\*.css" />
</ItemGroup>
<Message Text="Compressing JavaScript files" Importance="high" />
<!--
Compress the JavaScript files.
Not the usage of %(JavaScript.Identity which causes this task to run once per
.js file in the JavaScriptFiles item list.
For more info on batching: http://sedotech.com/resources#Batching
-->
<Packer InputFiles="%(_JavaScriptFiles.Identity)"
OutputFileName="#(_JavaScriptFiles->'$(_PackageTempDir)\Scripts\%(RecursiveDir)%(Filename)%(Extension)')"
Mode="JSMin"
Verbose="false"
Condition=" '#(_JavaScriptFiles)' != ''" />
<Message Text="Compressing CSS files" Importance="high" />
<Packer InputFiles="%(_CssFiles.Identity)"
OutputFileName="#(_CssFiles->'$(_PackageTempDir)\Content\%(RecursiveDir)%(Filename)%(Extension)')"
Mode="CSSMin"
Verbose="false"
Condition=" '#(_CssFiles)' != '' "/>
</Target>
</Project>
Here I’ve created one target, CompressJsAndCss, and I have included AfterTargets=”CopyAllFilesToSingleFolderForPackage” which causes it to be executed after CopyAllFilesToSingleFolderForPackage. Inside this target I do two things, gather the files which need to be compressed and then I compress them.
1. Gather files to be compressed
<ItemGroup>
<_JavaScriptFiles Include="$(_PackageTempDir)\Scripts\**\*.js" />
<_CssFiles Include="$(_PackageTempDir)\Content\**\*.css" />
</ItemGroup>
Here I use an item list for both JavaScript files as well as CSS files. Notice that I am using the _PackageTempDir property to pickup .js & .css files inside the temporary folder where the files are written to be packaged. The reason that I’m doing that instead of picking up source files is because my build may be outputting other .js & .css files and which are going to be published. Note: since the property _PackageTempDir starts with an underscore it is not guaranteed to behave (or even exist) in future versions.
2. Compress files
I use the Packer task to compress the .js and .css files. For both sets of files the usage is pretty similar so I will only look at the first usage.
<Packer InputFiles="%(_JavaScriptFiles.Identity)"
OutputFileName="#(_JavaScriptFiles->'$(_PackageTempDir)\Scripts\%(RecursiveDir)%(Filename)%(Extension)')"
Mode="JSMin"
Verbose="false"
Condition=" '#(_JavaScriptFiles)' != ''" />
Here the task is fed all the .js files for compression. Take a note how I passed the files into the task using, %(_JavaScriptFiles.Identity), in this case what that does is to cause this task to be executed once per .js file. The %(abc.def) syntax invokes batching, if you are not familiar with batching please see below. For the value of the output file I use the _PackageTempDir property again. In this case since the item already resides there I could have simplified that to be #(_JavaScriptFiles->’%(FullPath)’) but I thought you might find that expression helpful in the case that you are compressing files which do not already exist in the _PackageTempDir folder.
Now that we have added this target to the .wpp.targets file we can publish/package our web project and it the contained .js & .css files will be compressed. Note: Whenever you modify the .wpp.targets file you will have to unload/reload the web project so that the changes are picked up, Visual Studio caches your projects.
In the image below you can see the difference that compressing these files made.
You can download the entire project below, as well as take a look at some other resources that I have that you might be interested in.
Resources
http://sedotech.com/content/samples/CompressBeforePublish.zip
http://sedotech.com/resources#batching
MSBuild BeforeTargets/AfterTargets
WebDeploymentToolMSDeployHowToExcludeFilesFromPackageBasedOnConfiguration.aspx
Packer.NET
For this to work in visual studio 2015, we have to change the "AfterTarget" from
<Target Name="CompressJsAndCss" AfterTargets="CopyAllFilesToSingleFolderForPackage">
to the following
<Target Name="CompressJsAndCss" AfterTargets="PipelineCopyAllFilesToOneFolderForMsdeploy">
enjoy!!
Related
A project (original VS format) is converted to use Package References. Great.
Project References "don't support content". Questionable choice, although not debated here.
Some used packages contain "content" and cannot or will not be updated to use "content files". Hmm..
What semi-automated tooling / method can be used to copy the "content" from NuGet dependencies that "don't support contentFiles"+? Only applying to direct packages is fine.
+Naturally, one could manually open up each individual NuGet file and copy the contents. This question isn't about "why" the switch was made and/or any merits or trade-offs and/or how how packages "should" be authored. The question is about an automated or semi-automated method to be able to restore the "content" into a project's source tree.
It is possible to use "content"-based NuGet packages with original Visual Studio / MSBuild projects that have been converted to use Package References.
The presented solution can likely be amended for SDK-style projects as well. The feat is accomplished by utilizing the GeneratePathProperty attribute of PackageReference+. Using the generated Pkg* path properties ensures valid paths to the referenced packages. (Note: if a package name contains ., replace it with _ in the Pkg* property name.)
First, add a GeneratePathProperty to all the packages with content to copy or link.
<PackageReference Include="bootstrap" GeneratePathProperty="true">
<Version>Don't ask..</Version>
</PackageReference>
<PackageReference Include="AngularJS.Core" GeneratePathProperty="true">
<Version>..it's not great</Version>
</PackageReference>
"Get the Content"
Depending on needs there are two different approaches presented here. While these approaches can be used together, neither approach should be applied to "content files"-based NuGet packages.
Approach #1: Copy Content to Project Source
With this approach it's expected that the files are added to source control. The following lines in the project can thus be un-commented (and re-commented) when updating packages to ensure that the newest NuGet package content.
It uses the Copy Task to, well, copy the the content.
<ItemGroup>
<NugetContentToRestore Include="
$(Pkgbootstrap)\Content\**\*.*;
$(PkgAngularJS_Core)\Content\**\*.*;
" />
</ItemGroup>
<Target Name="BeforeBuild">
<Copy SourceFiles="#(NugetContentToRestore)"
DestinationFiles="#(NugetContentToRestore->'$(ProjectDir)\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
If the ItemGroup is not commented out during normal development, the items will be included in the the project’s solution explorer root, which is a bit ugly. It’s also not possible to detect when "content" has been removed.
Approach #2: Link in Content
If none of the content is to be copied and/or checked into source control along with the project, it can also be linked in. This approach has the benefit of always linking in the latest content and does not pollute the solution explorer.
A local file explicitly included in the project will take precedence over the linked resources.
For a Normal Project (and bin-deployed files)
For a normal project, <CopyToOutputDirectory> is sufficient and the linked files will go in the 'bin' output. However, this does not work for a Web Project which only understands files which are physically present in the project tree.
<ItemGroup>
<Content Include="$(Pkgbootstrap)\Content\**\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(PkgAngularJS_Core)\Content\**\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
For a Web Project (requires local files)
For a Web Project, these links must be copied to local files: <CopyToOutputDirectory> is not sufficient or required. Packages with content that should go into the bin output should use the approach above.
<ItemGroup>
<Content Include="$(Pkgbootstrap)\Content\**\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Content>
<Content Include="$(PkgAngularJS_Core)\Content\**\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Content>
</ItemGroup>
Then add a target to copy the linked files so they are picked up by the Web Project tooling. See Copying linked content files at each build using MSBuild. It may be relevant so skip certain links and add source control ignore entries.
<Target Name="CopyLinkedContentFiles" BeforeTargets="Build">
<Copy SourceFiles="%(Content.Identity)"
DestinationFiles="%(Content.Link)"
SkipUnchangedFiles='true'
OverwriteReadOnlyFiles='true'
Condition="'%(Content.Link)' != ''" />
</Target>
+This works in an up-to-date Visual Studio 2019 environment. It may not work in certain older MSBuild/NuGet configurations as Pkg* property generation is required.
Tooling or method to re-add 'content' to projects utilizing “Package
References”
I am afraid that there is no such tool to make files from content folder into projects with PackageReference nuget management format.
As the user of the package, there is currently no tool to copy the files of the content folder to the projects with PackageReference.
And actually, if there is such a tool, it also violates the mechanism of nuget. All of them are depended on the author of the nuget package rather than the users unless the package is authored by us
Suggestion
So you have to manually unzip the nuget package and copy the files into your project.
And it seems to be a bit complex and if you still want to get a easy way to get what you want, I suggest you could suggest a feature on our User Voice Forum.
And after that, you could share the link here and anyone who is interested in it will vote it so that it will get more Microsoft's attention. All of these will help get what you want as soon as possible.
In our web application, I'm co-opting the iu-Cans-CA locale as a pseudo-locale where string resource keys are used as the values as well. This is to provide an aid for automation testing. To generate the .resx for this locale, I'm using a T4 template.
The .resx is generated correctly, but something's wrong: the resource file isn't being compiled into the assembly like the other real translations are. I finally figured out the cause. Because the T4 template is used to generate the .resx, the .csproj contains this:
<EmbeddedResource Include="Strings.iu-Cans-CA.resx">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Strings.iu-Cans-CA.tt</DependentUpon>
</EmbeddedResource>
For some reason, this is preventing the .resx from compiling. If I update the .csproj to this, it does compile:
<EmbeddedResource Include="Strings.iu-Cans-CA.resx">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</EmbeddedResource>
I was hoping this would be the end of it, but now whenever the .csproj is loaded in Visual Studio, the project file is once again updated to the former structure, breaking the compilation of the resource file again.
Is there a way to fix things so that the generated/dependent .resx properly compiles as an embedded resource? Or is there a way to permanently break the dependency relationship so it doesn't keep getting restored? I realize I can rewrite the .tt so it generates a file with a different name, but I'd prefer finding a solution that doesn't require this since that is an atypical usage of T4 and I want to avoid too many WTFs.
The workaround is to strip the DependentUpon metadata from the T4 generated EmbeddedResource elements before the build really kicks off. The following modification of the BeforeBuild target in my project file works for me:
<Target Name="BeforeBuild">
<!-- Fix MSBuild issue where T4 generated .RESX files do not get compiled properly into satellite assemblies -->
<ItemGroup>
<GeneratedEmbeddedResource Include="#(EmbeddedResource)" Condition="'%(EmbeddedResource.DependentUpon)' != ''">
<DependentUpon></DependentUpon>
</GeneratedEmbeddedResource>
<EmbeddedResource Remove="#(EmbeddedResource)" Condition="'%(EmbeddedResource.DependentUpon)' != ''"/>
<EmbeddedResource Include="#(GeneratedEmbeddedResource)" />
</ItemGroup>
</Target>
You need to specify an output path for the .resx file.
If you leave Output.Project empty and don't specify a directory/path in Output.File, the generated file will be DependentUpon the .tt file itself.
this is my situation:
I have VS2010 solution with X projects included.
Wix project that can create msi from all compiled artifacts.
I have build machine \ Jenkins that first compile (MSBuild .Net 4) all the solution, then compile the wix to package it to msi.
What\how can I inject to all artifacts\dlls the number of the product (e.g 11.2.0.4789) - as simple as possible?
Is there and command line arguments that can be passed while compiling the solution?
There are tools, such as several extensions for MSBuild, that do version stamping but each assumes a particular workflow. You might find one that works for you but a DIY method would help you evaluate them, even if it isn't your final solution.
You can add a property to the MSBuild command-line like this:
msbuild /p:VersionStamp=11.2.0.4789
Note: I assume you are going to parameterize the Jenkins build in some way or generate the number during a preceding build step. Here is a simulation of that:
echo 11.2.0.4789 >version.txt
set /p version=reading from pipe <version.txt
msbuild /p:VersionStamp=%version%
Now, the work is in getting each project to use it. That would depend on the project type and where you want VersionStamp to appear.
For a csproj, you might want to use it as the AssemblyVersion. The simplest way is to move the attribute to a cs file by itself and rewrite it every time. I would leave a comment in AssemblyInfo.cs as a clue to where it now comes from. You can include the cs file in your project either dynamically or permanently. I prefer dynamically since it is effectively an intermediate file for the build. So, in your .csproj add the following in a text editor (e.g. Visual Studio. Unload and Edit project):
<Target Name="BeforeBuild">
<PropertyGroup>
<AssemblyVersionPath>$(IntermediateOutputDir)AssemblyVersion.cs</AssemblyVersionPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(AssemblyVersionPath)" />
</ItemGroup>
<WriteLinesToFile
File='$(AssemblyVersionPath)'
Overwrite="true"
Condition="'$(ProductVersion)' != ''"
Lines='using System.Reflection%3b;
[assembly: AssemblyVersion("$(VersionStamp)")]' />
</Target>
This is sufficient but a more thorough solution would include adding the file to a list so it is cleaned with other files and only writing the file if the version changed to prevent unnecessary rebuilds, etc.
Use a similar technique for other project types.
I got a question about the build process on VS.
I have a DLL Project with 3 .cs files inside. I would like to be able to define which files to include in the compilation process. So I can build a dll file only with file1.cs. Or file2.cs. Or file1.cs and file3.cs.
For now, the only way I know, is to create as many proj file I need and choose manually which one to build.
I would like to know if there are any other way to do it, maybe in the prebuild event you can redefine the files included or not. Maybe some addin exists ?
Thanks.
You can conditionally include files in a project file by putting a condition on the itemgroup.
Open up your csproj in a text editor:
<ItemGroup>
<Compile Include="Program.cs" Condition="$(BuildtypeA)!='True'" />
<Compile Include="Program1.cs" Condition="$(BuildtypeA)!='True'" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
Use whatever condition you like- in this case BuildtypeA. You can pass this in as aproperty on the commandline to msbuild. (or my creating.modifying a build configuration)
You can also use wild cards on a given folder and it will include in the project file what ever is at a given location.
<ItemGroup>
<Compile Include="..\dynamiccontent\*.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
No add-in required.
Not exactly sure why you couldn't just compiles the project with all 3 files all the time, but if you really need to do something like this, you can check out the MSBuild extensions. They support the ability to manipulate an Xml document (which is what a csproj file is) and you can dynamically add/remove the reference to the files as needed. Let me know if you need an example of doing something like that.
I have some content files that I would like to share between a number of projects in Visual Studio.
I have put these files in their own project, set the build action to "Content", and the copy to output directory to "Copy if newer". I would like all these files to be copied to the bin/debug directory of the projects that reference them.
I can get it to work by including a reference to the "contents" project in each of the projects that need the files, but that requires that a minimal assembly be generated (3K). I assume there is a way, using MSBuild, to make this all work without creating the empty assembly?
Thanks to everone who took the time to make a suggestion about how to solve this problem.
It turns out that if I want my compiled content files to be treated like content files (in that they get copied to the output directory of any other project that references my project), I need to create a target which runs before GetCopyToOutputDirectoryItems, and add the full path of the compiled content files to the AllItemsFullPathWithTargetPath ItemGroup. MSBuild calls GetCopyToOutputDirectoryItems for projects on which the current project depends, and uses the resulting file list to determine the files that are copied along with the assembly.dll. Here is the XML from my .csproj, just in case someone else has a similar problem.
I have a custom task called "ZipDictionary", and I accumulate all the files that I am going to compile in an ItemGroup called DictionaryCompile. My target, "FixGetCopyToOutputDirectoryItems" is executed before "GetCopyToOutputDirectoryItems". I don't do the actual compilation there, since this target can be called multiple times by referencing projects, and it would hurt performance. The target does some transforms to get the post-compilation file names, and then returns the full paths to all the files, since relative paths will not work when copy is called from the referencing project.
<ItemGroup>
<DictionaryCompile Include="Dictionaries\it-IT.dic">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</DictionaryCompile>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<UsingTask TaskName="ZipDictionary" AssemblyFile="..\LogicTree.DictionaryCompiler\bin\Debug\LogicTree.DictionaryCompiler.dll"/>
<Target Name="BeforeCompile">
<Message Text="Files #(DictionaryCompile)" Importance="high" />
<ZipDictionary DictionaryFiles="#(DictionaryCompile)" OutputDirectory="$(OutputPath)">
<Output TaskParameter="OutputFiles" ItemName="DictionaryOutputFiles" />
</ZipDictionary>
</Target>
<Target Name="FixGetCopyToOutputDirectoryItems" BeforeTargets="GetCopyToOutputDirectoryItems">
<ItemGroup>
<_DictionaryCompile Include="#(DictionaryCompile->'$(OutputPath)Dictionaries\%(FileName).ltdic')" />
</ItemGroup>
<AssignTargetPath Files="#(_DictionaryCompile)" RootFolder="$(MSBuildProjectDirectory)\$(OutputPath)">
<Output TaskParameter="AssignedFiles" ItemName="_DictionaryCompileWithTargetPath" />
</AssignTargetPath>
<ItemGroup>
<AllItemsFullPathWithTargetPath Include="#(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'" />
<_SourceItemsToCopyToOutputDirectoryAlways Include="#(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='Always'" />
<_SourceItemsToCopyToOutputDirectory Include="#(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'" />
</ItemGroup>
</Target>
A better possible solution would be to
place a common directory in the solution dir and place your common content files there.
in VS, in each project that should share this content, right-click add existing item, browse to the desired item(s), select, click the down-arrow on the add button and select add as link. In the project, you will notice the files are added with a 'shortcut' overlay.
In the project, select the newly added links and right-click->properties and select Build Action: content, Copy To Output Directory: Copy Always.
This is a simple solution to the problem given.
I use this technique for things like SQL scripts and partial config files (using configSource) with great success. This allows me to make changes to these files in a single location with the assurance that they will be propigated throughout the solution.
A more robust solution would be to create a project with embedded resources. This requires a bit more work to manage the content on the receiving end but may be worth it in the long run as having a bunch of loose artifacts flying about can become problematic.
Hope that helps.
A similar solution like the one Sky suggested can be found in my answer to "Is there a way to automatically include content files into asp.net project file?".
It allows to share your content but you must not touch the folder or its content inside VS because this breaks the recursive path.
This approach works best for auto-generated content - you don't have to bother about including new content files to your solution.
And of course you can reuse this in multiple solutions/projects.
We do something similar where we have "...ReleaseBuilds" that reference dlls and content we require for specific projects. Compiling copies everything to the bin debug folder and indeed creates the empty assembly.
Within Visual Studio we have a post-build event in the "...RealeaseBuild" (in project properties) that copies/deletes or run batch files to make sure we have all the files (configs, services etc etc) required and to delete the empty assembly.
HTH