Grunt is a JavaScript task runner that can be used to automate (among other things) the various tasks around building and deploying JavaScript: concatenation, minification, JSHint, QUnit. etc. etc.
There are plenty of example configurations for the tasks above – particularly this example from the grunt documentation – but I wanted to do one more step at the end of my build: create a NuGet package.
Setup
For this example, let’s assume that we have the following structure:
+ src
- source1.js
- source2.js
+ dist
- compiled.js
- compiled.min.js
- grunt.js
- package.json
Here we have 2 source files (under src
) that have been compiled into compiled.js
and compiled.min.js
under the dist
folder. For the purposes of this example it doesn’t matter whether they have been created manually or have been generated by an earlier grunt task; we just want to make sure that they are there.
In addition to these we have gruntfile.js
and package.json
which define the available grunt tasks and the package. The grunt docs cover these files in detail so I’m not going to go over the initial contents, but they are included below for reference.
package.json
{
"name": "my-library",
"version": "0.1.2",
"devDependencies": {
"grunt": "~0.4.1"
}
}
The important things to note here are the definition of the package name and package version – we will use those later.
gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json')
//other task configuration here
});
grunt.registerTask('default', [
/*other tasks here*/
]);
};
The gruntfile would ordinarily contain the definitions of all of the other tasks that have been configured, but for clarity I have omitted them.
The NuGet Definition
The first step in automatically generate our NuGet package is to create a definition for the package in a Nuspec file. This is an XML document with some pretty-much self explanatory fields:
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<!--
ID used to identify the nuget package -
e.g. ProxyApi -> https://www.nuget.org/packages/ProxyApi
-->
<id>my-library</id>
<!--
Package version - we are going to leave this blank for now
-->
<version>0.0.0</version>
<!-- Author, Owner and Licensing details -->
<authors>Steve Greatrex</authors>
<owners>Steve Greatrex</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<copyright>Copyright 2013</copyright>
<!-- General Information -->
<description>Helper extensions for KnockoutJs</description>
<releaseNotes>-</releaseNotes>
<tags>JavaScript KnockoutJS</tags>
<!--
Packages (incl. minimum version) on which this package
depends
-->
<dependencies>
<dependency id="knockoutjs" version="2.1.0" />
<dependency id="jQuery" version="1.8.0" />
</dependencies>
</metadata>
<files>
<file src="dist\compiled.js" target="content\scripts" />
<file src="dist\compiled.min.js" target="content\scripts" />
</files>
</package>
Important things to note here:
-
Dependent packages are listed under
package/metadata/dependencies
. These define which other NuGet packages will be installed as prerequisites to your package- Package outputs (i.e. what will get inserted into the project) are listed under
package/files
. - File locations are relative to the path from which we will run NuGet.exe
- For content files (i.e. not a referenced assembly), paths are relative to a special “content” path which refers to the root of the project. In the example above, the files will be added to the scripts folder in the project root
- Package outputs (i.e. what will get inserted into the project) are listed under
We can now pass this file to NuGet.exe and it will generate a .nupkg
package for us that is ready to be published. To test this, let’s put a copy of NuGet.exe alongside the saved my-library.nuspec
file and run the following command:
nuget pack my-library.nuspec
Voila – my-library.0.0.0.nupkg
has been created!
Invoking NuGet from Grunt
In order to invoke this command from our grunt script we will need to create a task. To keep things simple we will use the basic syntax for the custom task:
grunt.registerTask('nuget', 'Create a nuget package', function() {
//do something here
});
The implementation of task just needs to call the NuGet.exe as we did above, but with a couple more parameters. We can achieve this using grunt.util.spawn
to asynchronously invoke a child process.
grunt.registerTask('nuget', 'Create a nuget package', function() {
//we're running asynchronously so we need to grab
//a callback
var done = this.async();
//invoke nuget.exe
grunt.util.spawn(
{
cmd: 'nuget.exe',
args: [
//specify the .nuspec file
'pack',
'my-library.nuspec',
//specify where we want the package to be created
'-OutputDirectory',
'dist',
//override the version with whatever is currently defined
//in package.json
'-Version',
grunt.config.get('pkg').version
]
},
function(error, result) {
//output either result text or error message...
if (error) {
grunt.log.error(error);
} else {
grunt.log.write(result);
}
//...and notify grunt that the async task has
//finished
done();
}
);
});
Things to note here:
- This code should appear in
gruntfile.js
after the call toinitConfig
but before the call toregisterTask
- Arguments to NuGet.exe are specified in the
args
array - The
-Version
argument is used to override the value from the.nuspec
file with whatever has been loaded frompackage.json
. This avoids the need to define the version number in multiple locations.
With this in place we can add the nuget task to the default task definition from our original gruntfile and we are ready to go.
grunt.registerTask('default', [/*other tasks here*/ 'nuget']);
Fire up the shell run the grunt command, and a nuget package will be created for you:
$ grunt
Running "nuget" task
Attempting to build package from 'my-library.nuspec'.
Successfully created package 'dist\my-library.0.1.2.nupkg'.
Done, without errors.
Combine this with concatenation, minification, unit tests and static code analysis and you have a very quick one-hit process to go from source to publication!