In my previous blog I wrote about how we can use Gulp to compile SASS. This time it’s all about how we can use Gulp in Visual Studio to prepare files for a release by minifying and concatenating them. I’ve put up a project using the standard MVC 5 template for this series of posts here
- Part 1: Setting up Gulp with Visual Studio
- Part 2: Getting SASSy
- Part 3: Deployment: minification and concatenation
- Part 4: Code quality: tests and linting
- Part 5: Thoughts on Gulp
Small is beautiful
To increase the speed of a site, and to save bandwidth, we want to:
- Have as few requests for the smallest files possible
- Cache these files for as long as possible
We can achieve this by concatenating and minifying the files, then adding a hash of the files contents to the filename so they can be cached indefinitely.
There are a number of different ways of approaching minification and concatenation with Gulp, and a lot of different plugins and plugin combinations. The following is a style that has worked for me, you may need to adjust it to suit your own workflow.
Plugins
There are a number of plugins we will need:
- gulp-useref for concatenation
- gulp-uglify and gulp-minify-css for minifcation of JavaScript and CSS respectively
- gulp-rev and gulp-rev-replace for revisioning
- gulp-filter for filtering files to process them independently
Lets add these to the package.json:
{ "scripts": { "install": "echo Done" }, "devDependencies": { "gulp": "~3.9.0", "gulp-load-plugins": "~0.10.0", "gulp-useref": "~1.3.0", "gulp-uglify": "~1.2.0", "gulp-minify-css": "~1.2.1", "gulp-rev": "~5.1.0", "gulp-rev-replace": "~0.4.2", "gulp-filter": "~3.0.0" } }
Referencing scripts and CSS
Now we’ll want to add our CSS and JavaScript files to our pages. These are put in commented build blocks that let gulp-useref know where a section it should concatenate is. So, at the top of the page (here it’s the _Layout.cshtml page) we reference the CSS:
<!-- build:css /dist/site.css --> ย ย ย <link href="/Content/bootstrap.css" rel="stylesheet" /> ย ย ย <link href="/Content/Site.css" rel="stylesheet" /> <!-- endbuild -->
In the first comment, we define two things:
- The first part, “build:css” tells useref that this be a CSS block
- The second is the location of the concatenated file that will eventually be referenced (more on this later)
We do the same at the bottom with the JavaScript:
<!-- build:js /dist/libs.js --> ย ย ย <script src="/Scripts/Libraries/jquery.js"></script> ย ย ย <script src="/Scripts/Libraries/bootstrap.js"></script> ย ย ย <script src="/Scripts/Libraries/respond.js"></script> <!-- endbuild -->
We can have multiple build blocks of the same type (e.g. multiple JavaScript blocks) so we can have one for libraries (Angular, jQuery etc) which will change less frequently than the code we write.
The Gulp task
Now for the biggie – the actual task itself. I’ll put it here and go through it in more detail below:
gulp.task('minifyFilesForRelease', function () { var cssFilter = plugins.filter('**/*.css', { restore: true }); var jsFilter = plugins.filter('**/*.js', { restore: true }); var assets = plugins.useref.assets(); gulp.src('./**/*.cshtml') .pipe(assets) //Process JavaScript .pipe(jsFilter) .pipe(plugins.uglify()) .pipe(plugins.rev()) .pipe(assets.restore()) .pipe(jsFilter.restore) //Process CSS .pipe(cssFilter) .pipe(plugins.minifyCss({ keepSpecialComments: 0 })) .pipe(plugins.rev()) .pipe(assets.restore()) .pipe(cssFilter.restore) .pipe(plugins.useref()) .pipe(plugins.revReplace({ replaceInExtensions: ['.js', '.css', '.html', '.cshtml'] })) .pipe(gulp.dest(function (data) { return data.base; } )); });
Here’s a line by line breakdown of what’s happening:
- Line 1: Define the task
- Line 3-4: Setup some filters we will use later to separate out the CSS and JavaScript for individual processing
- Line 6: Reference the assets object of useref which will return the build blocks we setup earlier
- Line 7: Get all our cshtml files
- Line 8: Get all our build blocks in these files
- Line 11: Filter our build blocks to just the JavaScript
- Line 12: Minify the JavaScript
- Line 13: Revision the JavaScript
- Line 14-15: Remove the filters, so we have access to all our build blocks again
- Line 18: Filter our build blocks to just the CSS
- Line 19-21: Minify the CSS removing any and all comments
- Line 22: Revision CSS
- Line 23-24: Remove the filters again
- Line 26: Replace the commented, unprocessed build block in our cshtml files with the processed ones
- Line 27-29: Add the revision number to the processed files (note we need to include cshtml files here)
- Line 30-32: Replace the original cshtml files. We need to specify a destination here, and as we have views in multipleย folders we can use this technique byย Yiling to overwrite a file regardless of its location
Phew, there’s a lot there! If you run this task each build block in our cshtml files will be replaced with a single, minified, revisioned file.
Before:
After:
Running in Release mode
We only want to run this task when we’re putting together a release, whether that’s manual or (much better!) automated. To do this, add a PreBuildEvent that runs the Gulp task before a release build. Go to the project properties, then Build Events and add the following to “Pre-build event command line”:
if $(ConfigurationName) == Release ( cd $(ProjectDir) npm install gulp minifyFilesForRelease )
This will then run our minifying task only on a Release build.
Lastly, as we are adding the minified files to a “dist” folder that doesn’t exist during development, we’ll need to add this folder after each build. Open up the csproj for the web project, and at the very end add the following:
<Target Name="AfterBuild"> <ItemGroup> <Content Include="dist\*.*" /> </ItemGroup> </Target>
This will include the contents of our dist folder after each build.
Wrapping up
We now have an automated process for minifying and concenating files that doesn’t intefere with the development process. The commit for everything is here.
Happy Gulping!
Thanks for the great article but the pre build script breaks the build on Visual studio online ๐
Hi Alex, unfortunately I haven’t used Visual Studio online so can’t say for sure why this happening. Does it come up with any error messages?
Hi Edward,
Yes, I’m trying to fix it now.
Will email you the fix as soon as I get a green build. ๐
Don’t you have to instance of _Layout.cshtml? One that has all the configuration and the one that gets all the minified stuff for production. It’s not clear were you keep the two, how do you separate them? How do you switch between them?
Hi Eugene – there is only one _Layout.cshtml page in the usual Views/Shared folder. You can see the file as it is today here. In there, there is a section at the top for CSS and one at the bottom for JavaScript. For example, here’s the JavaScript block:
< ! -- build:js /dist/libs.js - - >
< script src="/Scripts/Libraries/jquery.js">< / script >
< script src="/Scripts/Libraries/bootstrap.js">< / script >
< script src="/Scripts/Libraries/respond.js">< / script >
< ! -- endbuild - - >
(NB I’ve had to put some extra spaces in there as WordPress doesn’t like the code!)
The Gulp task will then run on a release build, and change this block into one minified and concatenated file which is then referenced in the same _Layout.cshtml page. So the entire block above will get replaced with the following:
< script src="/dist/libs-204324b1dd.js" >< / script >
So, we only have one _Layout.cshtml page with sections that get replaced when we build for a release.
Hope this answers your question!
Hey Edward
These are great articles! Been looking at bunch of blogs and articles but none of them give a clear bigger picture – especially when it comes to how to handle the development and deployment evironments – min vs not to min etc
2 questions:
In the previous post you have the scss to css – so i’m guessing for the above to work you are checking-in your css along with scsss to source control? With multiple devs working and updating same scss files – the css changes can be quite noisy. I suppose it the release build could do those steps too.
Are you planning to write the next 2 articles in this series? Would be nice to know how you fit testing+linting into the CI pipeline and gulp
Hi Jay, thanks for the nice comment! To answer your questions:
1) I agree that is you’re working with a CSS pre-processor then ideally you don’t want to check in the generated CSS – they’re basically build artifacts. This is exactly what I ended up doing here, but annoyingly have since moved on from the project and forgot to write it up!
From memory, I referenced the generated CSS in my Views but didn’t include them in source control. I modified the csproj to add the generated CSS in the “BeforeBuild” Target section, but don’t believe there were any other changes. If it doesn’t work let me know and I’ll spend some time digging into it.
2) Yes, I’ve actually had the 4th part mostly written and sitting in my drafts for a while now, but just started a new contract which has eaten up my time. I promise I will finish it off and publish ASAP!