Generating files in a Vue application using Hygen
Automation of repetitive tasks improves productivity and gives little allowance to human errors that arise due to repetition and I am here to tell and show you how.
Growing to become a senior software engineer has prompted a lot of change to the processes I take in writing code starting from my file structure, to my directory structure to the way I structure my tests, to my code quality, and a lot more. Recently, I got to a point where I had to change my file structure and write the unit test of a component directly beside the component for ease of access and to prompt me to write unit tests for all my components. As time went on, The process of creating the files along with their unit tests and initializing the files with the needed or default structure started becoming repetitive, boring, and a waste of time. I searched for tools to solve this issue and immediately fell in love with Hygen.
Hygen is a scalable code generator that saves time.
In this blog post, we would be making use of Hygen in a vue-ts application, but it can be used with any tech stack at all. As a matter of fact, it was created specifically to serve react and node.js developers but here we are using it in a vue-ts application 😅.
For the sake of this project, we would be sticking strictly to Hygen and not going deep into the initialization of other technologies.
Prerequisites
- Node.js: to make use of NPM for the global installation.
- A project of any stack: But for the sake of this blog post, we would be making use of Vue. to create a Vue project, go through the documentation.
Let’s Begin
First and foremost, we would need to add Hygen to our device globally. For this, we would make use of NPM by running this command in your terminal:
$ npm i -g hygen
This would install Hygen globally on your device. For non-Javascript developers, there are other ways to install Hygen but we won’t be going through that in this tutorial. Nonetheless, the Hygen Documentation has a comprehensive tutorial on that. Next, we would proceed into the folder we want to set up for the initialization of Hygen. To initialize Hygen in a project, we would run this command in the root directory:
hygen init self
This command would automatically generate a _template directory in the root directory of your project.
Generators
A generator is used to stop repetitive tasks. A generator is responsible for generating files containing default codes known as templates.
To create a generator, we would run the command:
hygen generator new [GENERATOR_NAME]
Preferably or for readability, [GENERATOR_NAME] should be the name of what you are trying to automate. For example, if I want to create a generator to create single components, I would run the command:
hygen generator new component
This would generate a component directory in the _template folder:
To invoke a generator, we run the command:
hygen [GENERATOR_NAME] new [FILE_NAME]
For example, if we are trying to create a component using the component generator, we would run the command:
hygen component new [THE_COMPONENTS_NAME]
This would automatically run the component generator with [THE_COMPONENTS_NAME] as the name variable. Which can be accessed in the template using the [name] variable as can be seen in the template section.
Interactive Prompts
Prompts are used for creating interactive generators like the one below.
Most times, prompts are used to select or configure what the generator should generate.
A prompt can be created by creating a prompt.js file in the generator directory:
The prompt.js exports an array as a module. In this array, questions that need to be asked would be placed as objects in the array. examples:
const _ = require("lodash");module.exports = [{type: "input",name: "name",message: "Name:",validate(value) {if (!value.length) {return "Components must have a name.";}const fileName = _.kebabCase(value);if (fileName.indexOf("-") === -1) {return "Component names should contain at least two words to avoid conflicts with existing and future HTML elements.";}return true;},},{type: "multiselect",name: "blocks",message: "Blocks:",initial: ["script", "template", "style"],choices: [{name: "script",message: "<script>",},{name: "template",message: "<template>",},{name: "style",message: "<style>",},],validate(value) {if (value.indexOf("script") === -1 && value.indexOf("template") === -1) {return "Components require at least a <script> or <template> tag.";}return true;},},];
The above configuration asks for the name of the file and validates it. After which it puts forth a multi-select to select the options you may want in the file to be created. The options being put forth in this example are:
["script", "template", "style"]
Templates
A Hygen template is a header of a markdown-like frontmatter and a body of an EJS templating engine. The template is used to specify the location of the file(using the frontmatter):
---to: "src/components/<%= h.changeCase.pascal(name) %>.vue"---
and the contents of the file using an EJS templating engine:
<%if (blocks.indexOf('script') !== -1) {%><script lang="ts">import { Component, Vue } from "vue-property-decorator";@Component({name: "<%= h.changeCase.pascal(name) %>",})export default class <%= h.changeCase.pascal(name) %> extends Vue {}</script><%}if (blocks.indexOf('template') !== -1) {%><template><div :class="$style['c-<%= h.changeCase.kebab(name).toLowerCase() %>']"></div></template><%}if (blocks.indexOf('style') !== -1) {%><style lang="scss" module>.c-<%= h.changeCase.kebab(name).toLowerCase() %>{}</style><%}%>
Putting it together:
---to: "src/components/<%= h.changeCase.pascal(name) %>.vue"---<%if (blocks.indexOf('script') !== -1) {%><script lang="ts">import { Component, Vue } from "vue-property-decorator";@Component({name: "<%= h.changeCase.pascal(name) %>",})export default class <%= h.changeCase.pascal(name) %> extends Vue {}</script><%}if (blocks.indexOf('template') !== -1) {%><template><div :class="$style['c-<%= h.changeCase.kebab(name).toLowerCase() %>']"></div></template><%}if (blocks.indexOf('style') !== -1) {%><style lang="scss" module>
@import "@/styles".c-<%= h.changeCase.kebab(name).toLowerCase() %>{}</style><%}%>
This template would suffice for a standard vue-ts file or component.
EJS
Like I said above, EJS is what’s used in the template of a generator. It is used for embedding Javascript templates.
In EJS, javascript can be written by placing the expression in this format
<%= [VARIABLE] %>
<% [JAVASCRIPT_EXPRESSION] %>
In the template, we are using EJS to set the name of the file dynamically.
---to: "src/components/<%= h.changeCase.pascal(name) %>.vue"---
and naming the vue.js class:
export default class <%= h.changeCase.pascal(name) %> extends Vue {}
One more thing to note about generators
Generators generate any EJS file found in its root directory, hence if you are like me and would want to write components and unit tests in the same folder, you can do so by Creating a unit.ejs.t file in the component generator. e.g:
Leaving a unit test empty without a describe or it testing block, throws a linting error which is very disturbing when developing. Hence we would place the default code into the unit test template:
---to: "src/components/<%= h.changeCase.pascal(name) %>.spec.js"---<%const importName = h.changeCase.pascal(name)%>/* eslint-disable no-undef */import { shallowMount } from "@vue/test-utils";import <%= importName %> from "./<%= importName %>.vue";describe("@components/<%= name %>", () => {it("Write a test here", () => {const { element } = shallowMount(<%= importName %>, {});// Write tests here});});
Testing what has been done
To test what we have done, we would invoke the generator by running the command:
hygen [GENERATOR_NAME] new [FILE_NAME]
Conclusion
The developer experience this has given me and my team in projects has been immense and really helpful, it has helped preserve the standard of the project in the sense that everyone gets to use the default structure accepted by the team, it has also helped in eradicating some human errors caused by carrying out processes numerous times especially when working in a large team. Hence, I would really encourage all teams to make use of this awesome piece of tech.