Code Generator Neat Hack
Tags: webdev, tutorial, productivity, javascript
Published: 2022-09-16 13:09:49

In this week’s short post I’d like to share with you a neat hack for the [Plop code generator](https://plopjs.com/) to overcome the “relative paths challenge”. 

When it comes to software development, a good developer experience (or DX) is a key aspect in helping the development process to become faster and more efficient.
Having a good DX means that developers are focusing on the real deal while other, less important stuff, is being taken care of by automated tools.
Such a tool is [Plop](https://plopjs.com/) which I really love for its simplicity and efficiency.

***

## Plop
Plop helps generate scaffold files for projects by presenting the developer a set of guiding questions and populating predefined templates with the input data. 

In one of my previous article, called “[Creating a React Component Generator](https://dev.to/mbarzeev/creating-a-react-component-generator-20l)”, I’ve described how you can use Plop to generate a React component in an existing project, which will include the component file, a css file for it, Jest test, Storybook story and a Cypress test, all just by giving the component name to the tool as input. 

This is a very powerful tool which can help medium and large RnD orgs to save development time while maintaining the same standard for all components.

## The Relative Paths Challenge
This challenge is not only related to Plop but to any file/code generator out there. Here it is:

Say you have a monorepo and you would like to have a package generator that will create a new package and wire it up with all the common configurations the monorepo might have (you can read more about examples of such configurations [here](https://dev.to/mbarzeev/sharing-configurations-within-a-monorepo-42bn) and [here](https://dev.to/mbarzeev/how-to-configure-babel-for-your-monorepo-j3c)).

These shared configurations usually allow a package to inherit a common configuration from the project’s root and extend it if required. I guess you can already see where the problem is -

Plop is working with Handlebars templates (.hbs files) in which it registeres the content of the files, along with some dynamic data which came from the developer’s answer , but here we’re dealing with paths to the configuration files, and these paths depend on the location of the package directory within the project - this is not something we would like our developers to be handling, nor should they.

For example, if we take the [components package under my Pedalboard monorepo](https://github.com/mbarzeev/pedalboard/tree/master/packages/components) and check it’s TS configuration file, you will see that it extends a base configuration `../../tsconfig.base.json`:

```json
{
   "extends": "../../tsconfig.base.json",
   "compilerOptions": {
       "module": "CommonJS",
       "outDir": "dist/cjs",
       "declarationDir": "dist/types",
       "jsx": "react"
   },
   "files": ["index.ts"],
   "include": ["src/**/*"]
}
```

But if we decide that our generator will allow us to create packages in nested directories (meaning, not only under “/packages” but “/packages/my/nested/package”) then the Plop generator will create a hard-coded path which is broken.

So how do we solve it? How can we create a generator which knows to generate relative paths?

## The Solution
First of all we are counting on the fact that one of the questions the Plop generator “asks” the developer is “where does this package reside on the project?”.
The answer to this question comes in the format of a file path, and let’s assume that it registers to a variable called “targetPath” in Plop.

Now, for calculating the relative path we’re going to use a Handlebars Helper, which is a reusable code we can then use in any template we wish, and it will get executed with the arguments we will pass to it.

I will call our helper “relativePath” and register it to Plop. Here is its code:

```javascript
const path = require('path');
 
plop.setHelper('relativePath', (from, to)=>{
   const fromPath = `${from}/package`;
   return path.relative(fromPath, to);
});
```

This helper function gets 2 args - 
from: It is the file path that would be used as base path.
to: It is the file path that would be used to find the relative path
This function will return a string with the normalized form of the resolved path using the [`path.relative` API](https://www.geeksforgeeks.org/node-js-path-relative-method/).
(I’m using a fake package name called “package” here since we need to simulate a nested path of a package. The name is really not important here)

Let’s see how we actually use it in our Plop’s handlebars template:

```handlebars
{
   "extends": "{{relativePath targetPath ‘./tsconfig.base.json’}}",
   "compilerOptions": {
       "module": "CommonJS",
       "outDir": "dist/cjs",
       "declarationDir": "dist/types",
       "jsx": "react"
   },
   "files": ["index.ts"],
   "include": ["src/**/*"]
}
```

We call the relativePath function and pass the “targetPath” we got from the developer (remember?) and the location of the base configuration path.

Now the generated files will contain the correct paths according to their location in the project’s tree. Pretty simple yet can save you a lot of headaches of “why doesn’t my new package resolve the path to my common Babel configuration?!”

## Wrapping Up
Generators, and Plop in particular, are powerful tools that can make our development life much easier without investing too much into them. In this post you saw how you can make Plop’s templates even more robust.
As always, if you have other ideas on how to make files and code generators more efficient, or just have some questions about what was brought here, be sure to share with the rest of us in the comments section below.

*Hey! If you liked what you've just read check out <a href="https://twitter.com/mattibarzeev?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">@mattibarzeev</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> on Twitter* :beers:

<small><small><small></small></small></small>