Sails is all about automating repetitive tasks to make your programming easier and Generators are no exception. Generators are command line utilities within Sails that automate the generation of files through templates within your Sails projects. In fact, Sails core uses generators to create Sails projects. So when you type...
~/ $ sails new myProject
...sails is using generators to build up the initial folder structure of a Sails app like this:
myProject
|_api
|_assets
|_config
|_node_modules
|_tasks
|_views
.gitignore
.sailsrc
app.js
Gruntfile.js
package.json
README.md
Other examples of generators in Sails core (meaning they are built into Sails) include:
sails generate api
To begin the process of generating a generator you can use sails-generate-generator
.
Note: The idea of creating a generator by invoking a generator may seem like some kind of demented infinite loop but trust us it will not create a worm hole to an evil alternate universe.
First we need a Sails project. If you haven't already created one go to your terminal and type:
~/ $ sails new myProject
cd
into myProject
or from any existing Sails project and create a generator from the terminal named awesome by typing:
~/ $ sails generate generator awesome
You'll know the generator was created if you see the message: info: Created a new generator!
.
To enable the generator you need to tell Sails about it via \myProject\.sailsrc
. If you were using an existing generator you would link to an npm module in .sailsrc
and then just install it with npm install
. Since you're developing a generator, you'll link to it directly. To create the link go back to the terminal and cd
into the awesome
generator folder and type:
~/ $ pwd
The pwd
command will return a fully resolved path to the generator (e.g. /Users/irl/sails_projects/myProject/awesome
).
Copy the path and then open myProject/.sailsrc
. Within the modules
property add an awesome
key and paste the path to the awesome
generator as the value.
Note: you can name the generator anything you want, for now let's stick with
awesome
:
{
"generators": {
"modules": {
"awesome": "/Users/irl/sails_projects/myProject/awesome"
}
}
}
Note: Whatever name you give your generator in the
.sailsrc
file will be the name you'll use from the terminal command-line to execute it.
Lastly, you'll need to do an npm install
from the terminal in order to install the necessary modules that were added to the generator's package.json
file.
Back at the terminal type: sails generate awesome example
. Let's take a look at what was generated.
Open up your project in a text editor you'll notice that a folder called hey_look_a_folder
was created and a file named example
was also created:
/**
* This is just an example file created at Wed Jun 04 2014 17:35:59 GMT-0500 (CDT).
*
* You can use underscore templates, see?
*/
module.exports = function () {
// ...
};
The folder and file illustrate the power of the generator not only to create elements but to use arguments
from the command-line to influence their content. For example, the file name, example
, used an element from the command line argument sails generate awesome example
.
All of the configuration for the awesome
generator is contained in \myProjects\awesome\Generator.js
. The main parts of Generator.js
are the before()
function and the targets
dictionary.
Note: We refer to the JavaScript object that uses
{}
as a dictionary.
before()
functionLet's take a closer look at myProject/awesome/Generator.js
:
...
before: function (scope, cb) {
// scope.args are the raw command line arguments.
if (!scope.args[0]) {
return cb( new Error('Please provide a name for this awesome.') );
}
// scope.rootPath is the base path for this generator
if (!scope.rootPath) {
return cb( INVALID_SCOPE_VARIABLE('rootPath') );
}
// Attach defaults
_.defaults(scope, {
createdAt: new Date()
});
// Decide the output filename for use in targets below:
scope.filename = scope.args[0];
// Add other stuff to the scope for use in our templates:
scope.whatIsThis = 'an example file created at '+scope.createdAt;
// When finished, we trigger a callback with no error
// to begin generating files/folders as specified by
// the `targets` below.
cb();
},
...
Each generator has access to the scope
dictionary, which is useful when you want to obtain the arguments that were entered when the generator was executed.
In your default awesome
generator a new key, createdAt:
was created in the scope. We'll take a look at this dictionary within a template momentarily.
...
// Attach defaults
_.defaults(scope, {
createdAt: new Date()
});
...
Next, the arguments used when executing the awesome generator (e.g. sails generate awesome <theargument>
) are available in an array from scope.args
. In our default awesome
generator a filename
property was added to the scope and assigned the value of the first element of the scope.args
array (e.g. example):
...
scope.filename = scope.args[0];
...
Finally, another property (e.g. scope.whatIsThis) was added to the scope dictionary.
...
scope.whatIsThis = 'an example file created at '+scope.createdAt;
...
Now, let's take a look at the targets
dictionary in myProject\awesome\Generator.js
to better understand how the folder (e.g. hey_look_a_folder) and file (e.g. example) were generated.
...
targets: {
// Usage:
// './path/to/destination.foo': { someHelper: opts }
// Creates a dynamically-named file relative to `scope.rootPath`
// (defined by the `filename` scope variable).
//
// The `template` helper reads the specified template, making the
// entire scope available to it (uses underscore/JST/ejs syntax).
// Then the file is copied into the specified destination (on the left).
'./:filename': { template: 'example.template.js' },
// Creates a folder at a static path
'./hey_look_a_folder': { folder: {} }
},
...
The template
and folder
helpers look a lot like routes. These helpers perform the actions that their names indicate.
Not surprisingly the template helper creates files based upon a template. Remember, that the scope dictionary is accessible to the templates.
...
'./:filename': { template: 'example.template.js' },
...
The left-hand side specifies the path and filename where as the right dictates which template the generator will use to create the file. Notice you're using the filename
from the scope.filename
assignment that was based upon the the first element of scope.args
in the before()
function. The templates can be found in myProject\awesome\templates
. In the awesome generator you're using example.template.js
:
/**
* This is just <%= whatIsThis %>.
*
* You can use underscore templates, see?
*/
module.exports = function () {
// ...
};
Note: the scope property
whatIsThis
which as you may recall uses the createdAt: property created in thebefore
function.
The folder helper generates folders.
...
'./hey_look_a_folder': { folder: {} }
...
The left-hand side specifies the path and name of the folder. The right-hand side specifies any optional parameters. For example, by default, if a folder already exists at that location an error will be displayed:
Something else already exists at ::<path of folder>
. If you want the generator to overwrite an existing folder you have two options. You can alter the folder helper to overwrite the existing folder by specifying force: true
in the options parameters:
...
'./hey_look_a_folder': { folder: { force: true} }
...
You can also use the --force
parameter from the command-line when executing the generator which will configure all helpers to overwrite:
~/ $ sails generate awesome test --force
To leverage the work of other programmers, generators were designed to be used by other generators. This is where the scope dictionary being passed down to all generators becomes really powerful.
For example, Sails core has a generator called sails-generate-model
. Since it's built into Sails core, there's no installation necessary. To add it to our awesome generator example is simple. Within the myProject\awesome\Generator.js
include it by inserting ./': ['model'],
...
targets: {
// './:filename': { template: 'example.template.js' },
'./': ['model'],
// './hey_look_a_folder': { folder: {} }
},
...
Note: By using
./
as the path, any models will be placed in the\api\models
folder from whatever folder the generator was executed.
That's it! Now let's create a model from within the awesome generator. From the terminal type:
~/ $ sails generate awesome user name:string email:email
If you take a look in myProject\api\models
you'll see a new file named User.js
has been created that contains the model attributes specified earlier.
/**
* User
*
* @description :: TODO: You might write a short summary of how this model works and what it represents here.
* @docs :: http://sailsjs.com/#!documentation/models
*/
module.exports = {
attributes: {
name : { type: 'string' },
email : { type: 'email' }
}
};
To publish the awesome generator to npmjs.org go into the myProject\awesome\package.json
file and change the name, author and any other meta information (e.g. licensing).
From within the myProject\awesome
folder at the terminal type:
~/ $ npm publish
Note: If you don't already have an NPM account, go to npmjs.org and create one.
To unpublish the module, type:
~/ $ npm unpublish --force
Change the myProject\.sailsrc
to:
{
"generators": {
"modules": {
"awesome": "whatever you named the module in package.json"
}
}
}
From the awesome generator folder within the terminal type:
~/ $ npm install
And you're all set!