Oops…this is not one of my posts…I invited a friend of mine, Vincenzo, to write about using Node.js tools with Visual Studio. Enjoy!
The presence of NodeJs on TFS online hosted build controller and the recent installation of git client in the platform (suggested by a tweet of mine: https://twitter.com/D3DVincent/status/480968128227074049), and on AppHarbor too (http://support.appharbor.com/discussions/problems/60449-git-binary-in-command-line) opens up new interesting possibilities of integrating Visual Studio and TFS online with the awesome Node JS tools.
In this article I will show you how to set up your Visual Studio and TFS online environment to take advantage of all this.
Note: this is not a NodeJs/tools tutorial. I will assume you have a knowledge about nodejs, npm, bower, tsd, gulp and the packages you're going to install. I will only show you the integration with visual studio.
1) At first, let's make sure to have nodejs installed (www.nodejs.org) with NPM installed and its directories installed into the PATH environment variable.
2) Install a git client (required by bower and tsd) and insert its executables into the PATH environment variable (the current installation package does not set this for you automatically). The most used one on Windows is msysgit (http://msysgit.github.io/), you can find it on WebPlatform too.
Let's make sure that all commands are now available on the command line, by typing its names on the console.
Thanks to this tip, we have now available on Visual Studio console all the tools we need to perform out nodejs tasks.
Prior to see the following steps, I will spend few words on how Npm works.
Unlike nuget, npm has the concept of local and global packages. For this reason, every package installed through this tool can be installed as global (-g flag on the commands). This will make the package available on every directory of your dev machine.
The other option has the same behavior of nuget packages: it will create on your project a new directory called node_modules in which all packages will be stored. You will find a useful .bin directory to all them.
Usually, having packages installed globally is very convenient. In addition, nodejs is smart enough to redirect your commands to the local node_modules directory, if any. This will prevent the usual compatibility issue (more on this, http://blog.nodejs.org/2011/03/23/npm-1-0-global-vs-local-installation)
Let's now open Visual Studio and work with the new tools installed. We will start with a brand new Asp Net empty web application project.
Unfortunately the package manager console looks like unable to handle interactive commands. So we will have to switch on command line some time.
Go to the package manager console and type this command
npm init
The command will ask us few questions about our new webapplication and write a package.json file for us.
This json file will track all installed package and will be used as a source for restoring packages during a build.
Visual Studio does know nothing about package.json, since we're using an external tool. This file must be included in our source control. I prefer to keep it in the solution too, but this is up to you. Let's go to the solution explorer, show all files and include in solution the package.json file
Now we have npm installed with a repository of over 80 thousand packages. You can browse all of them here: https://www.npmjs.org/.
For a modern web application, I always install these packages from npm repository:
1)Bower (package manager for javascript libraries)
2)Tsd (typescript definitions for javascript libraries)
3)GulpJS (task and automation tools)
npm install bower --save
npm install tsd --save
npm install gulp --save
The --save flag instructs npm to write this installation to packages.json file. If all was done correctly, our file should look like this:
Thanks to these entries, we will be able to perform a package restore on TFS and on our dev machines too.
Since I do not have global packages installed, I have to point the bin directory of node_modules folder.
Let's init tsd too:
And do not forget to include json files into solution.
We're done with interactive commands. We can close command prompt and work comfortably from visual studio and its package manager console.
Next step are just to install packages and tools: I will take jquery and angularjs from bower, their typings from tsd and set up a simple minify task with gulp:
The installed components are, obviously, external to Visual Studio. We have to include them into the project:
In my example, I will include only the highlighted voices. Anyway, the strategy is up to you. If you will select these files, they will be published on Azure or your WebDeployment enabled server. If not, you will have to extend the MsBuild task to include these files.
Now we have a more or less complete development solutions. Install other packages and code your solutions.
Question from .NET developers: why do not use nuget package manager to install DefinitelyTyped Typescript definitions? Well, actually nuget is not able to restore content folder from its packages and developers are not going to cover this use case (you can find more about this issue and why it won’t be fixed here: http://nuget.codeplex.com/workitem/2094. Due to this, we are forced to include into our checkin typings file too. Even if a lot of persons suggests that this is the right thing to do, I do not like this kind of procedure. Switching to the cliend side, I can remove and restore these kind of packages as well and keep under source control only MY app files.
Done that, is time for automation.
At first, let's configure exclusions for TFS source control. We have to add a new .tfignore file in root of our solution and add the following lines:
\NodeJsTools\bower_components
\NodeJsTools\typings
This will exclude these folders from source control, even if they are included into solution file. We do not need to add node_modules since it's completely external.
This means that our hosted source code will miss all nodejs modules and all bower/tsd reference files. In this way, our application will never work (and won't pass the build, since the Typescript target installed into tfs controller will fail: it won't find definitions for angular and jquery).
Here is where MsBuild can help us. We will inject our dependencies task directly on BeforeBuild target.
Tfs controller have got nodejs installed (and npm too) with a git client (needed for bower).
Let's open our project file (.csproj) and let's paste this.
<Target Name="BeforeBuild">
<Exec Command="npm install" />
<Exec Command="node_modules\.bin\tsd reinstall" />
<Exec Command="node_modules\.bin\bower install --config.interactive=false" />
</Target>
This target, which will run before build, will install all modules listed into packages.json, then all definitions in tsd.json and all the ones of bower.json. In this way, all references will be fine and the build will pass. Note that I'm using local packages in node_modules since I cannot assume that packages will be installed on tfs hosted build controller.
Let's add these lines too:
<Target Name="AfterBuild">
<Exec Command="node_modules\.bin\gulp $(Configuration)" />
</Target>
These will run my gulp task depending on configuration. In Release, for example, I use to do angular template cache, html and js minification and things like this. In Debug, I usually run jasmine tests. The decision is up to you.
Let's now extend the Clean target to delete also all nodejs stuff. Insert into a PropertyGroup this tag:
<CleanDependsOn>
$(CleanDependsOn);
CleanNodeFiles;
</CleanDependsOn>
And then let's define this target:
<Target Name="CleanNodeFiles">
<ItemGroup>
<TypeScriptGenerated Include="%(TypeScriptCompile.RelativeDir)%(TypeScriptCompile.Filename).js" Condition="!$([System.String]::new('%(TypeScriptCompile.Filename)').EndsWith('.d'))" />
<TypeScriptGenerated Include="%(TypeScriptCompile.RelativeDir)%(TypeScriptCompile.Filename).js.map" Condition="!$([System.String]::new('%(TypeScriptCompile.Filename)').EndsWith('.d'))" />
</ItemGroup>
<Delete Files="@(TypeScriptGenerated)" />
<RemoveDir Directories=".\build\;.\bower_components\;.\typings\"/>
<Exec Command=".\tools\DelFolder .\node_modules" />
</Target>
These lines will delete all typescript generated files and all bower/tsd files.
You may see that node_modules directories and subdirectories are deleted using a DelFolder script and not through the remove dir. This is because node_modules are very deep directory and RemoveDir target cannot delete paths longer that 256 characters. For this reason, I use to include a script in my source control (not in the project, however) with these lines:
@echo off
if \{%1\}==\{\} @echo Syntax: DelFolder FolderPath&goto :EOF
if not exist %1 @echo Syntax: DelFolder FolderPath - %1 NOT found.&goto :EOF
setlocal
set folder=%1
set MT="%TEMP%\DelFolder_%RANDOM%"
MD %MT%
RoboCopy %MT% %folder% /MIR
RD /S /Q %MT%
RD /S /Q %folder%
endlocal
As final task, we may want to add custom files (generated by gulp js tasks for example). We can do it with other few lines of msbuild code:
<Target Name="DefineCustomFiles">
<ItemGroup>
<CustomFilesToInclude Include=".\build\**\*.*">
<Dir>./</Dir>
</CustomFilesToInclude>
</ItemGroup>
</Target>
<Target Name="CustomCollectFiles">
<ItemGroup>
<FilesForPackagingFromProject Include="@(CustomFilesToInclude)">
<DestinationRelativePath>%(CustomFilesToInclude.Dir)\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
These lines instruct the deployment to include all build folder and place it into the root.
A typical use case is this one:
Usually you have got your html files into solutions and using gulp, you minify your views. Of course you will not save these changes into your project root, or your source files will be modified and not usable (since they are minified).
For this reason, you may want usually to place all modified files into another folder, (in my case, build). Of course, Visual Studio does know nothing about these files since they are completely external to your webapplication. Thanks to this task, we will include these files into WebDeployment and place it into root folder, replacing the "development" files.
This task can be useful to include into deployment the bower_components folder (selecting the appropriate scripts) and removing from your .csproj all the folder reference. This choice is up to you. However, I usually prefer to keep scripts reference into solutions to always have a clear overview of what my webapplication needs to run up.
Ok, let's casting out nines: clean your project and verify that all nodeJs folders are gone. It may take a bit since the robocopy script is slower than a simple directory delete. Then build your project and all the stuff should be in its place.
You can find all the source code for this demo cloning this repository: https://github.com/XVincentX/NodeVStudio
Good. We're done. Have fun.
P.S: The project is evolving: have a look here -> https://github.com/XVincentX/NodeJsMsBuild
P.S2: I was mentioned in Morning Brew and Asp.Net site too!
Tags: NodeJs, Visual Studio, Gulp