Project setup

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.

package.json

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, and

3. 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"
  }
}

Tip

As displayed in this example, when installing modules from npm, we can also include multiple module names at once npm install module1 module2 module3.

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"
}

Tip

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.

Gruntfile.js

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.

Tip

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.

Directory structure

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.189.23