For the past few months, I have been looking for a way to define some JS and CSS files that would be shared between multiple projects in an ASP.Net solution. The intent is to define common scripts and CSS in one place instead of trying to keep multiple copies of it in sync or implementing an internal CDN with a versioning scheme. The challenge is finding a way to do this with a minimum of impact on the development, deployment, and production processes.
The Shared Project
Yesterday the best solution I was also the best one I had thought of on my own, which was to create a shared project and use pre- or post-build commands to copy the common files to the relevant web projects. Unfortunately this doesn’t solve the “let me edit a file without rebuilding” unless I then edit the copied file, test, and remember to paste the changes back into the source without first building and wiping out my temporary changes.
This wasn’t going to make life that much easier.
Start with just the CSS?
This morning, while looking into the potential of using LESS or SASS to reduce the repetitiveness of the files, I realized I had the potential for a much better solution. If I had a way to compile a LESS or SASS file on the fly into a CSS file, then I could still put my common CSS in a central location and just use an import statement in a template in each project to pull those common values in.
I initially looked at using SASS with the Mindscape Web Workbench plugin. This seemed like a good solution, but something I read recently about design-time T4 templates led me to wonder if someone had created a T4 template that would transform LESS or SASS syntax into a nice clean CSS file.
What did we do before search engines…
Implement T4CSS Template
Phil Haack (blog|twitter) posted a blog in 2009 on exactly this topic. He created a T4 template for Visual Studio 2008 that would use the dotless C# assembly to convert LESS files to static CSS files and provided to the .Less site.
Now we’re cooking.
First we need to download the t4css package from github: https://github.com/dotless/dotless/downloads
There are two files we are concerned with, the dotless.Core DLL and the T4CSS.tt template file. The template file is placed in our CSS folder in our site.
Referencing the Assembly
Unfortunately Visual Studio 2010's T4 implementation no longer accesses assemblies through the project references, but this still leaves us with a few options. Given that I want to share this among several projects, I put the dotless.Core DLL in a folder at my solution level and updated the path in the T4 template to use the solution path macro.
Next I created a sample file to play with, which I called test.less.css (fancy, I know). I also modified the settings section of the T4CSS.tt file, setting _runOnBuild and _useCssExtension to "true". This will cause the template to run on each build, as well as when I trigger it, and it will look for files ending in ".less.css" instead of just ".less". This gives us some CSS intellisense with minimal hassle, though Mindscape's Web Workbench apparently handles this out of the box and there is an extension to make VS treat the less extension as a CSS format.
If we keep the T4CSS file open, it will mark itself as unsaved each time it runs, so using Ctrl+Shift+S will save it and regenerate the output CSS. At least that's the theory. Unfortunately in my case, it seems that the template file was being saved prior to the css file, so I've taken to pressing Ctrl+S and then Ctrl+Shift+S after making a quick change in my CSS (which is still way better than a Rebuild All would be).
Note: There is also the "Transform All Templates" button on the top of the solution explorer if I don't feel like double-saving. I could also add a shortcut in the keyboard commands list (Tools -> Options -> Environment -> Keyboard) for "TextTransformation.TransformAllTemplates
Working Across Projects
This solution hasn't quite given me the "save the file and refresh the page" ease of use of a static CSS file. This means if you are editing a less file that more than one template references, and you don't have all the templates open, you could get out of sync. To help keep things clean in source control, this means you should run a complete build (to let all the transforms run) or use.
To make this work for multiple projects we can add a folder at the solution level with our less files and use the @import statement to pull them in. Except now we can't do the .less.css trick anymore because less doesn't process @import's ending in CSS, assuming they are intended to be regular css imports. At this point, it's probably time to stop fighting it, apply the "Less is CSS" extension I mentioned above, and change back to using .less instead of .less.css. Fun times.
So what we end up with is a folder structure that looks like:
/SolutionName/Common/dotless.Core.dll /SolutionName/Common/common.less /SolutionName/Project1/css/T4CSS.tt /SolutionName/Project1/css/stylesheet.less /SolutionName/Project2/css/T4CSS.tt /SolutionName/Project2/css/stylesheet.less
And inside the project-specific .less files we have an import at the top, like so:
@import "../../Common/common.less"; /* plus some project specific-stuff */
And there we have it, shared CSS. I posted a working sample project on BitBucket if you want to browse it in detail.