Now we have installed Node.js, npm and Grunt, we're ready to create our first Grunt environment. Let's say we've already built a website, now we want to use Grunt to minify our assets. In this section, we'll learn about the two required files: the package.json
file and the Gruntfile.js
file, as well as a recommended directory structure.
The package.json
file is a package descriptor; it is used to store all metadata about the module, such as name, version, description, author, repository, and more. It's the single file required to effectively use npm, therefore, the package.json
file can also be thought of as the "npm file". As the file extension would suggest, it must be in the
JavaScript Object Notation (JSON) data format. If the JSON syntax is invalid, npm will display an error when reading this file. Using a package.json
file in our project has many benefits. These include: making it easy to reinstall our dependencies by defining a dependencies field; letting us publish our module to npm by defining the name
and version
fields, and storing common scripts related to the package by defining the scripts
object.
For a project using Grunt, the dependencies
property will be the most useful feature of the package.json
file. When we run the command: npm install
(without a proceeding package name), npm will look for our package.json
, parse it, then install each module listed in the dependencies
property.
Before we review an example package.json
file using the dependencies
property it is important to understand how all npm packages are versioned. Tom Preston-Werner proposed the Semantic Versioning specification (SemVer) in late 2009, due to what he describes as "dependency hell" – a situation that arises within large systems built with many smaller systems. The SemVer website (http://gswg.io#semver) contains the following short summary:
"Given a version number
MAJOR.MINOR.PATCH
, increment the:1.
MAJOR
version when you make incompatible API changes,2.
MINOR
version when you add functionality in a backwards-compatible manner, and3.
PATCH
version when you make backwards-compatible bug fixes.Additional labels for pre-release and build metadata are available as extensions to the
MAJOR.MINOR.PATCH
format."
Although module publishers aren't required to strictly follow SemVer, npm does require them to ensure version numbers are in the correct format and incremented at each release.
The following is an exceptionally simple example of a package.json
file:
//Code example 03-npm-install { "dependencies": { "grunt": "0.4.2" } }
In this case, when we execute: npm install
alongside this package.json
file, it is equivalent to executing: npm install [email protected]
. The @
symbol followed by the version tells npm to install that specific version of the grunt
module. Grunt is a rare exception to rules above. Again, on to SemVer website:
"Major version zero (0.y.z) is for initial development. Anything may change at any time. The public API should not be considered stable."
Since Grunt is currently at version 0.4.2 and also adheres to SemVer, the Grunt team considers Grunt to still be in development, as the API has not been frozen yet. Some disagree with this decision as Grunt is used so widely across the Web development industry; however, this is a relatively inconsequential detail. Since Grunt has no major version to make use of, the minor version is incremented for backward incompatible changes. Therefore, the changes made from version 0.3.x to 0.4.x of Grunt are incompatible. To prevent automatic upgrades of major and minor versions, we'll use tilde-prefixed versions. The tilde symbol (~
) denotes any approximate version; this is a feature of npm, which can also been seen as an addition to SemVer to mitigate backward incompatible changes. The tilde prefix tells npm that it may only upgrade the given package for increments of the patch version. For example, if we first installed version 0.3.5 of the grunt
module, while also specifying: "grunt": "latest"
in our package.json
file, subsequent npm installs would yield the latest version. As previously mentioned, a change from version 0.3.5 to 0.4.2 (latest), would introduce breaking changes to our build. However, if we instead specified the approximate version: "grunt": "~0.3.5"
, subsequent npm installs would only upgrade us to a version matching 0.3.x, so currently, it would yield version 0.3.17.
For this reason, we should always specify the exact or approximate versions, we should never use "latest"
or "*"
(which means any version). To help us achieve best practice, a convenience option was added to npm for exactly this situation. When starting a project, and when we're installing a module for the first time, we can use the --save
option, which in addition to installing the module will also automatically update our package.json
file with the modules just installed and their latest approximate versions.
For example, if we started with an empty
package.json
file as follows:
{}
Then, if we executed: npm install --save grunt grunt-contrib-uglify
, it would currently update our previously empty package.json
file to the following code:
{ "dependencies": { "grunt": "~0.4.2", "grunt-contrib-uglify": "~0.2.2" } }
When we use npm install
, npm will retrieve the given module and then its dependencies, and repeat this step until all dependencies have been retrieved. If we were to publish our module with the grunt
module as a dependency, other modules, which depend on our module, would then be forced to download grunt
too. Grunt, however, is a build tool; it builds and transforms our source into a final product. This final product is what should be published for public use. Therefore, in this case, the grunt
module is a development-only dependency, and actually belongs in the devDependencies
field of our package.json
file. Luckily, there is also a
--save-dev
option, which will do exactly the same thing as the --save
option, but instead of placing the dependencies listed in the dependencies
field, it will use the devDependencies
field.
As we add more and more fields to our package.json
file, it can be tiresome setting it up again and we may be tempted to simply copy and paste the package.json
file into a new project. It's good practice, however, to build our own package.json
file for every project. This can be done easily with another npm feature, the npm init
command. npm init
is a setup "wizard" for creating package.json
files. Once executed, npm will prompt us for each common field. This is a good habit to get into as, in most cases, the only similar field across all your projects will be the author
field and there is an npm command for this too: npm config set init.author.name 'Jaime Pillora'
. This will set the default author for all subsequent npm init
. We especially should not copy and paste our dependencies
field when starting a new project. The time when there is no code to break is the perfect time to upgrade to the latest version of each package.
In summary, when starting a new Grunt project, first we should create our package.json
file with npm init
, and then we should add our dependencies (Grunt as well as the Grunt plugins we're using) and also lock their current (and newest) version, for example, npm install --save-dev grunt grunt-contrib-uglify
.
Once complete, we should have a package.json
file that looks similar to the following one:
//Code example 04-package-json { "name": "gswg-2-04-package-json", "version": "0.1.0", "repository": "https://github.com/jpillora/gswg-examples.git", "author": "Jaime Pillora <[email protected]>", "devDependencies": { "grunt-contrib-uglify": "~0.2.2", "grunt": "~0.4.2" }, "license": "MIT" }
We can confirm that we've created a valid package.json
file by pasting its contents into this Package.json Validator tool found at http://gswg.io#package-json-validator.
Just as the command npm install
looks for the package.json
file and fails without it, the grunt
command will look for the
Gruntfile.js
file using a similar method as well. Once found, Grunt will invoke this file with the grunt
global object. The Gruntfile.js
file can be seen as our build initializer – it will define configuration and set up tasks. It will not, however, contain the directive to run the build. This happens automatically once the Gruntfile.js
has finished executing. However, we can customize what is run, and we can provide extra options via the command line, as we will see in Chapter 3, Using Grunt.
On the getting started page on the Grunt website (http://gswg.io#grunt-getting-started), the Grunt team describes the following code as the Gruntfile.js
file "wrapper":
module.exports = function(grunt) { // Do grunt-related things in here };
We could view this syntax as the magic "wrapping" that all Gruntfile.js
files need in order to run; however, it is favorable to understand its purpose. If we recall our summary of CommonJS, we'll remember that the
module.exports
object is returned as the result of another module requiring it. Therefore, in this code, we're simply providing a function with a single parameter. Grunt will then call our function with the grunt
object as the single argument. The grunt
object is what we'll use to interact with Grunt. That is, it is Grunt's Application Programming Interface (API) – it contains the methods that have been exposed (or exported) for public use. The
grunt
object contains methods for updating and retrieving configuration (grunt.config
), methods for loading and registering tasks (grunt.task
), methods for reading and writing files (grunt.file
), and much more. The grunt
object also contains aliases to common functions, for example, grunt.config.init
can be called via grunt.initConfig
and grunt.task.registerTask
can be called via grunt.registerTask
.
Read more about the Grunt API at http://gswg.io#grunt-api.
Since Grunt is open source, when we're unsure about a particular feature, we can always visit the repository and read the source code at http://gswg.io#grunt-source-code. Each feature set has its own file. For example, the grunt.config
module and its methods can be found in the lib/grunt/config.js
file.
The purpose of the "wrapper" function that we provide is to initialize our Grunt configuration for use within our tasks, and to load and group our tasks for use with the command line.
Now that we have a package.json
file and installed our modules, we've just completed the prelude to Code example 01-minify
at the beginning of this book. There, we'll find a simple Gruntfile.js
file utilizing the grunt-contrib-uglify
plugin and the expected output from executing this build. In the next section on configuring tasks, we'll cover more complex use cases.
Now that we've created our package.json
and Gruntfile.js
files and installed our packages, we should have the following directory structure:
//Code example 05-directory-structure . ├── Gruntfile.js ├── package.json └── node_modules ├── grunt └── grunt-contrib-uglify
As an aside, if we're placing our project in a
Version Control System (VCS), we'll need to remember to exclude the node_modules
folder. For example, with Git we'd also include a .gitignore
file containing a line node_modules
.
Depending on the type of project, our source files may be structured differently, however, the common use case for Grunt is to transform our source files into our build
(or output
) files. So generally, we'll include all our source files in a folder called src
and then create another folder build
to house the result of this build. This clear separation is important because the build
folder can then be seen as temporary: it may be replaced at any time with a new set of files. Therefore, it's important we do not get our source
and build
files mixed up.
If we were to add a test suite to our project, we'd also have the test files in addition to our source
files. These test files exercise our build
files to ensure they are functioning as expected. Finally, once we've added our project-related files, we should be left with a directory structure similar to the following one:
//Code example 05-directory-structure . ├── Gruntfile.js ├── package.json ├── node_modules │ ├── grunt │ └── grunt-contrib-uglify ├── build ├── src ├── test └── .gitignore
Also, by excluding the build
folder from our VCS, we force all developers using this project to execute the build. This will highlight any machine-dependent build issues and ensure they are resolved early. Once in place, we have a trivial set-up guide to get started on our new project. Run npm
install
followed by grunt
.
18.191.189.23