How To Create a Slush Generator in 5 minutes


Slush — the streaming scaffolding system

Scaffolding refers to quickly setup a skeleton for new projects and applications. Basically, giving you a kickstart and remove the manual recurring tasks of setting up all configuration files, directories and common components for every new project. There are several scaffolding tools out there, Yeoman probably being the most widespread. Though the latest and greatest (in my humble opinion) is Slush due to its simplicity of creating your own generators based on gulp tasks.

“Slush is a scaffolding tool, i.e. a tool to help you generate new project structures to get you up and running with your new project in a matter of seconds.”

Install Slush and create a generator project

First we install Slush globally (you might need to use sudo).

npm install -g slush

Slush has a generator for scaffolding generators. To be able to use the slush-generator we need to install it (you might need to use sudo).

npm install -g slush-generator

Now we are all set to create our own generator. Create a directory to hold our generator — by convention generators should be prefixed with slush-.

mkdir slush-tutorial && cd slush-tutorial

Within the directory run the slush-generator. This will ask for a some input values (e.g. the name and description for your generator) and create all necessary directories and files.

slush generator

The created file structure is pretty simple.

  node_modules/
templates/
CHANGELOG
CONTRIBUTING.md
LICENSE
package.json
README.md
slushfile.js

slushfile.js is the engine. It is the one file that defines all commands that our generator should be able to do.

templates is the directory holding all our files that our generator will be based on.


Create a template file and run our generator

Lets create our first template file that our generator should generate for us.

Within the templates directory we create a package.json file. Since we want our generator to inject values into our generated files based on some user input we will use template handlers <%= someVariable %>.

file: templates/package.json
---
{
"name": "<%= appName %>",
"description": "<%= appDescription %>",
"version": "<%= appVersion %>",
"homepage": "https://github.com/<%= userName %>/<%= appName %>",
"author": {
"name": "<%= authorName %>",
"email": "<%= authorEmail %>"
},
"repository": {
"type": "git",
"url": "git://github.com/<%= userName %>/<%= appNameSlug %>"
},
"license": "MIT",
"main": "server.js",
"dependencies": {
"hapi": "^13.2.1"
},
"engine": {
"node": ">=5.0.0"
}
}

Before slush can run our generator we need to link our repository to our local npm registry. Within the slush-tutorial directory run:

npm link

Now lets try our generator. Create a new directory to where our project should be setup.

mkdir myapp && cd myapp

Start our slush generator by running:

slush slush-tutorial

After answering the questions the project should start installing dependencies. When this is done open myapp/package.json and see what it looks like. All our template handlers have been replaced with real values from our answers. Pretty cool!

Lets create server.js file and scaffold it with a hapi-server.

file: templates/server.js
---
'use strict';

const Hapi = require('hapi');

// Create a server with a host and port
const server = new Hapi.Server();
server.connection({
host: 'localhost',
port: 8000
});

// Add the route
server.route({
method: 'GET',
path:'/',
handler: function (request, reply) {
return reply('hello from <%= appName %>');
}
});

// Start the server
server.start((err) => {

if (err) {
throw err;
}
console.log('Server running at:', server.info.uri);
});

Run our generator again. Now we have created a fully functional hapi server pre-populated with a route that responds with “hello from myapp”.

Start it by running:

npm start

Handle .gitignore and other hidden files

One of the absolute key benefits of using a scaffolding tool is so that every new project has the same standardised configuration files (.eslint, .gitignore, .editorconfig). Files that should be hidden are prefixed with _ (underscore). Slush will automatically replace the underscore with a dot when scaffolding the project. Lets create a _gitignore file to exclude our node_modules.

file: _gitignore
node_modules

Re-run our generator again and see how slush generates a .gitignore file based on _gitignore.


Behind the scenes

Now we have seen what slush can do. Lets see how it does it.

Slush is actually Gulp, just with another name. The default task takes care of the initial scaffolding routine. In the file below I have commented each step.

gulp.task('default', function (done) {
## Define the input questions for the initial scaffolding
## and store the input into variables (e.g. appName).
var prompts = [{
name: 'appName',
message: 'What is the name of your project?',
default: defaults.appName
}, {
name: 'appDescription',
message: 'What is the description?'
}, {
name: 'appVersion',
message: 'What is the version of your project?',
default: '0.1.0'
}, {
name: 'authorName',
message: 'What is the author name?',
default: defaults.authorName
}, {
name: 'authorEmail',
message: 'What is the author email?',
default: defaults.authorEmail
}, {
name: 'userName',
message: 'What is the github username?',
default: defaults.userName
}, {
type: 'confirm',
name: 'moveon',
message: 'Continue?'
}];
## Using inquirer package to handle the actual prompt
inquirer.prompt(prompts,
function (answers) {
## When all questions are answered the callback are invoked
## and continue the routine if the variable moveon is tru
if (!answers.moveon) {
return done();
}
## Set a slug name for the app e.g. My App will be my-app
answers.appNameSlug = _.slugify(answers.appName);
## Select all files in all folders within the templates directory
gulp.src(__dirname + '/templates/**')
## Replace all <%= %> with corresponding answer variable
.pipe(template(answers))
## Rename all files prefixed with _ to use . instead
.pipe(rename(function (file) {
if (file.basename[0] === '_') {
file.basename = '.' + file.basename.slice(1);
}
}))
## Handle if files with same name already exists
.pipe(conflict('./'))
## Write the generated files to the destination folder
.pipe(gulp.dest('./'))
## Install npm packages 
.pipe(install())
.on('end', function () {
done();
});
});
});

Thats it! Continue to add more files to the templates directory and soon you will have your own project generator for scaffolding all your new apps. Let me know what generators you have created or which ones you use the most.

All code used in this blogpost can be found on github.

In part 2 we will go through how to create sub-generators.