Code Boilerplate With Ease or Getting Started With Go

Have you ever repeat something during the coding? If you are a developer, I bet you were thinking of automating those steps. Because we write code every day, automation becomes a part of our DNA, and we start thinking of automating everything around us. But do we do it?

Yes and no. Because most of us are quite lazy, we won’t start doing something if it is hard. If we have a simple tool to automate testing — we use it. We choose our favorite framework to automate some steps in building an app, and we pick a powerful tool to automate bundling of our application. What do we choose when we want to automate generating a boilerplate code?

The first tool that comes into mind is Yeoman. You’ve probably heard of it but did you use it? It helps you to create your own or install others boilerplates called “generators” and run them to create a new project. There are also sub-generators that allow you to generate code in an existing project. While it is a useful tool, you may reject an idea of using it due to its complexity.

There is a less popular tool called Plop. It impresses users by its simplicity: the only readme file is needed to learn how to use it. But at the same time, it lacks an installation step, so it is your responsibility to think about how to share and download a project boilerplate.

Introducing Go

I’m working on a tool called Go. Its mission is to do any kind of automation both: simple and pleasant. To do so I put together features of Yeoman with the simplicity of Plop, trying to avoid issues of both.

Go is mainly designed to do 3 things:

  1. Give you tools to automate your tasks (like managing files and collecting user input);
  2. Provide an interface to run those tasks;
  3. Let you create your own or use others project boilerplates.

While it still under development, there is a stable version that I want to show up to hear your opinion or even to receive a contribution.

Automation & Interface

Go is an NPM package that bundles few tools to create an interface and help you automate your everyday tasks. To start using it, you’ll need to install it and add a file, in the root of your project, where you will describe your tasks:

$ cd path/to/project
$ npm install -D go
$ touch gofile.js

Commands

The gofile.js is an entry point for the Go tool. We will register our tasks in there and describe what should they do. Let’s begin with a simple example:

gofile.js

const go = require('go')
go.registerCommand('ping', () => console.log('pong'))

We have registered our first task. There are a few ways how you can execute any executable NPM package including Go:

  • <name_of_executable> — when a package is installed globally
  • node_modules/.bin/<name_of_executable> — when it is only installed locally
  • npx <name_of_executable> — the other way to call it when it is installed locally (more about npx)

In this article, I’ll use it in a way as if Go is installed globally, but you may choose a different option from the list above. To tell Go to run a command it should be called with a name of the command: go ping.

Example of running Go command

We’ve just triggered our command using its name. The callback can also receive given arguments:

go.registerCommand('ping', ({ args }) => console.log(args))
$ go ping host --count 3
{ _: [ 'ping', 'host' ], count: 3 }

name and callback are required properties of every command, but there are also a few more properties that can be given using a different syntax:

go.registerCommand({
name: 'greet',
description: 'greets user with a given name',
options: {
short: { type: Boolean, alias: 's', default: false }
},
callback({ args }) {
const name = args._[1];
if (!name) console.error('name is not specified')
else if (args.short) console.log(`Hi, ${name}!`)
else console.log(`Hello, ${name}!`)
}
})

$ go greet Susan
Hello, Susan!
$ go greet -s Susan
Hi, Susan!
$ go greet
name is not specified

Do you wonder why do we need description? Try to call Go with no arguments, and you’ll see an interactive menu to navigate through all registered commands and execute them. Value of each description property has been printed after the related command name. Now you may ask: How to pass arguments to commands through this interactive menu?

Plugins

go.registerCommand({
name: 'greet',
description: 'greets user with a given name',
options: {
short: { type: Boolean, alias: 's', default: false }
},
async callback({ args }) {
const name = args._[1] || await go.ask('Enter name:');
if (!name) console.error('name is not specified')
else if (args.short) console.log(`Hi, ${name}!`)
else console.log(`Hello, ${name}!`)
}
})

Notice that the callback is now defined as an async function. That is because go.ask() returns a promise and await works much better in this case than then(). If you run the greet command from the interactive menu or from the CLI directly but without specifying a name, the name will be asked during the function execution.

The ask() method comes from the Quiz plugin. Actually, go itself doesn’t have any methods except use(<plugin>) to setup plugins and there are few plugins that are pre-installed with it: Quiz, Cli, Templates, and Fs. All of them extends go module to give you more features. You have seen a little of what Quiz and Cli (registerCommand() comes from it) plugins do. Fs plugin gives you several methods to work with files and folders and Templates plugin let you, as you may guess, process template files. We’ll come to it in a minute.

Re-use

Let’s write a code that will start a simple project and then we will see how can we share it:

gofile.js

const go = require('go')
const FRAMEWORKS = {
bootstrap: '^4.1.3',
bulma: '^0.7.1',
foundation: '^4.2.0'
}
go.registerCommand('install', async () => {
const [name, frameworkName] = await go.ask([
{ message: 'Name of the new project:',
validate: input => !!input.trim() },
{ message: 'Choose CSS framework:',
choices: Object.keys(FRAMEWORKS) }
])
  const framework = {
name: frameworkName,
version: FRAMEWORKS[frameworkName]
}
  // using Fs plugin to move the file
await go.copy('templates/package.json', 'package.json')
// using Templates plugin to render it with given context object
await go.processTemplates({ name, framework }, 'package.json')
// P.S. processTemplates can render one or multiple files to a different location
// so there will be no need to manually copy files
  // cleanup
await go.remove('templates')
await go.remove('package-lock.json')
})

templates/package.json

{
"name": "<%= name %>",
"dependencies": {
"<%= framework.name %>": "<%= framework.version %>"
}
}

Do not run install command yet. This time we’ll do it in a different way. We’ll deploy this boilerplate project to a different directory and it will itself call to install command. Let’s create a configuration file for this purpose:

.goconfig.json

{
"install": "npm install && go install"
}

Loaders

Now, using Go that is globally installed (or via npx), you can deploy your boilerplate to a different directory:

$ go dir path/to/boilerplate path/to/new/project

After running this command files from the boilerplate directory will be copied to the new project directory and install command from the configuration file will be executed.

go dir is the way to trigger Dir loader. Loaders are a different kind of Go plugins that extend its CLI toolset. Their main purpose is to load boilerplates from different sources. Go is bundled with few loaders that you can start using immediately: Git, GitHub, Gist, and Dir.

# load boilerplate from local directory
$ go dir source/path destination/path
# load gist
$ go gist 28some41valid02gist63hash
# load boilerplate from any Git source
$ go git git@bitbucket.org:username/repository.git
# load boilerplate from GitHub
$ go github username/repository
# Git and GitHub loaders has shortcuts so you can avoid using their names
$ go git@bitbucket.org:username/repository.git
$ go username/repository

As you may guess. you can upload the boilerplate to Git repository and everybody who has access to it can install it using Git loader.

Was it simple? Was it fun? Is it interesting? Do you have questions? Please, comment. If you have any ideas or suggestions let me know! You can mail me (contact@me.st), create an issue or contribute to a repo. Thanks!