cobalt-base
You are a programmer who wastes a lot of time on coding repetitive, boilerplate stuff. For example, you want all your React components to be saved in a specific way:
- You want them to be saved under
ui/Components
- You want them to have PascalCased filenames.
- You also want each component to have its associated css file saved under
client/stylesheets/
- And you want the stylesheets to have underscored_names.
So, something like this:
project
└───ui
│ └───Components
│ │ MyFirstComponent.jsx
│ │ MySecondComponent.jsx
│
└───client
│ my_first_component.css
│ my_second_component.css
What's more, you realize that you always have to code the same following lines for your react components:
; Component { super; } { return <div className="my-first-component"> </div> ; } MyFirstComponentpropTypes = ; MyFirstComponentdefaultProps = ;
What if you could type something like generate component my-first-component
in your terminal, and the rest was done automatically for you?
Enter cobalt
cobalt-base
is a tool for creating cli scaffolding tools. (It's a tool that creates tools - so meta)
Cobalt handles:
- Generating files:
... generate something somename
- Deleting files:
... remove something somename
- Overwriting: If the command you run might overwrite a file, cobalt asks if you're sure.
- Commandline option parsing
How
You only need to do 2 things: create templates and their generator logics.
- Create template at:
templates/something/index.ejs
Hi! My name is <%= name %>.I'm <%= age %> years old.
- Add generator logic at:
generators/something.js
module { filename: `.txt` parent: 'example_parent_folder' templateName: 'something.index' templateData: name: 'MY NAME' age: 'MY AGE' };
Now you can just run node projectName/index.js generate something my_first_something
, and this will create example_parent_folder/my_first_something.txt
with the following content:
Hi! My name is MY NAME.
I'm MY AGE years old.
Tutorial
Note: For the sake of brevity, this tutorial has the following command node projectName/index.js generate something
, shortened to ... generate something
In this tutorial, we will create a script that dynamically generates React components and their corresponding css files.
- Follow the instructions at https://github.com/keremkazan/cobalt-starter to get the boilerplate code.
- Create a file for the generator logic:
generators/component.js
- The filename is really important. It's how cobalt understands where to look. When you run
... generate something
on the cli, cobalt will look forgenerators/something.js
. Therefore when you run... generate component
, cobalt will look forgenerators/component.js
. - As described in the intro, we want a template for our React component, and another one for its corresponding css file. There are two types of React components: stateful and stateless. So let's actually create the following 3 templates:
templates/Component/stateful_component.ejs
templates/Component/stateless_component.ejs
templates/Stylesheet/component.ejs
Our project directory should now look like this:
myScaffold
│ index.js
│ package.json
└───node_modules
│ └───cobalt-base
│ └───...
└───generators
│ │ component.js
└───templates
│ └───Component
│ │ stateful_component.ejs
│ │ stateless_component.ejs
│ └───Stylesheet
│ │ component.ejs
- Let's populate these templates:
// templates/Component/stateful_component.ejs ; class <%= pascalCaseName %> extends ReactComponent { super; } { return <div className="<%= dashedName %>"> </div> ; } <%= pascalCaseName %>propTypes = ; <%= pascalCaseName %>defaultProps = ;
//templates/Component/stateless_component.ejs const <%= pascalCaseName %> = { };
//templates/Stylesheet/component.ejs <%= dashedName %>
- Now let's code our generator logic. Every generator file has one responsibility: Exporting a function that tells cobalt what files to create, where to save them and what to put inside them.
- This function takes in one param:
options
. This param will give us access to the variables we have typed on the cli. For example,... generate component my-First-component my_second-Component --stateless
will set the options variable to:
options argv: 'my-First-component' 'my_second-Component' stateless: true
- With that in mind, let's create the generator logic for the stateful component. In order to convert the user input to its camelCased, underscored, pascalCased etc. versions, we will use the corrected-names library. You don't need to worry about installing this library as
cobalt-starter
comes with it.
//generators/component.js const getCorrectedNames = ; module { const name = ; return filename: `.js` parent: 'ui/Components' templateName: 'Component.stateful_component' templateData: name ;}
filename
is the the name we want to give to the file. In our case, this will resolve to:MyFirstComponent.js
parent
is where we want to save this file. Since we want to keep all of our react components in one place, we can hardcode that.templateName
is the name of the ejs template that cobalt uses. In our case, that is'Component.stateful_component'
. Cobalt will automatically resolve this and understand that it will need to look fortemplates/Component/stateful_component.ejs
.templateData
is the data we want to pass to the template. If we look back at our template forstateful_component
, we'll see that it uses two variables:pascalCaseName
anddashedName
. Now let's look at the variablename
.
const name = /*name = { camelCaseName: 'myFirstComponent', pascalCaseName: 'MyFirstComponent', dashedName: 'my-first-component', underscoredName: 'my_first_component', originalName: 'my-First-component',}*/
Now, if we execute ... generate component my-First-component
on the cli, cobalt will generate the React component, put it under ui/Components
and give it the correct content. However, we also want to add stylesheets. This means that instead of a single object, our function should return an array of objects:
//generators/component.js const getCorrectedNames = ; module { const name = ; return filename: `.js` parent: 'ui/Components' templateName: 'Component.stateful_component' templateData: name filename: `.css` parent: 'client' templateName: 'Stylesheet.component' templateData: name ;}
We also need to consider stateless components:
//generators/component.js const getCorrectedNames = ; module {const name = ; return filename: `.js` parent: 'ui/Components' templateName: optionsstateless ? 'Component.stateless_component' : 'Component.stateful_component' templateData: name filename: `.css` parent: 'client' templateName: 'Stylesheet.component' templateData: name ;}
- Finally, we need to tell cobalt that
--stateless
is a boolean option. Otherwise, cobalt won't be able to understand the cli options. In order to parse the cli options, cobalt uses this wonderful library: command-line-args - Add the following line to your index.js before
run();
.
configOPTION_DEFINITIONS;
- Once we update the
index.js
page, it should look like the following:
const path = ;const run config = ; configTEMPLATES_DIR = path;configGENERATORS_DIR = path; configOPTION_DEFINITIONS; ;
And that's it. Now we can execute ... generate component my-first-component
and ... generate component my-second-component --stateless
. This will create the files exactly the way we want them to be.
Bin Executable
If you want to run your scaffolding tool as bin executable from the terminal, do the following:
- Give your tool a command name, and assign it in your package.json file via the
bin
key. For example, if you want your scaffolder to run when you executemyscaffolder generate component my-first-component
, replacestarter
withmyscaffolder
, so that you have the following in yourpackages.json
file:
"bin":
- Finally, you need to make your module globally accessible. There are two ways of doing this.
- While in the project directory, run
npm link
. This will create a global sym-link. - Publish your package on npm and install it globally
So now, instead of running node ~/.../my-scaffolder/index.js generate component my-first-component
on your terminal, you can just run myscaffolder generate component my-first-component
.