I write this blog knowing that right now everyone is fanning out on AI (Generative Pre-trained Transformer 3) for any automation around code, and that is great! If you would prefer to look into this topic more, I suggest starting with #openGPT on Twitter or OpenAI Turn comments into code & API example.
Plop - Looks to be quite popular for React file generation and is better suited to smaller file code generation tasks, But it can be used to generate entire apps.
Other considerations
Yeoman looks better suited to generating entire apps but can also generate files and is cross-cutting so your backend and your frontend can use the same code generator.
Yeoman would be my next recommendation if Plop doesn't quite fit your needs.๐Let me know if you would like to know more about the above-mentioned considerations, I can write another blog about them. ๐
Things to consider before deciding on any automated code generation.
At what level do you want to set conventions and maintain code generation? App folder level or the file level?
Generating an entire app, all or part of scaffolding is required to get developing quickly, but would also be used infrequently.
Files/folders in existing apps File code generation will have a high frequency of use for feature work and adding to existing apps, which could add the highest value.
Consider your current codebase, which provides the most value?
Does the answer lend itself to maintaining conventions & patterns for app folder scaffolding, file boilerplate or maybe both?
Frequent use is not always a great indication of the high value of auto-generating code. Through frequent implementation, consistency is higher, but if it is the same implementation over and over, we should automate it.
Infrequent implementation can be less consistent, meaning we should lean on a template for consistency.My recommendation; When starting with maintaining automated code generation templates, start at the file level. It is more forgiving, quicker to implement and easier to update ๐ช
The below examples only show how to generate new code, not how to update existing code.
Plop simple setup
package.json
{
"name": "plopCodeGen",
"version": "1.0.0",
"description": "collection of templates for generating file boilerplate or app skeletons",
"main": "plopfile.js",
"author": "me@hello.com",
"private": true,
"scripts": {
"new": "plop",
"new:app": "plop app",
"new:comp": "plop component",
},
"devDependencies": {
"plop": "^3.1.1",
}
}
plopfile.js
You will notice the first arg of the plop.setGenerator
(name,
config) matches the scripts in the package.json
import generateApp from './templates/appTemplate'
import generateComponent from './templates/componentTemplate'
module.export = function (plop) {
plop.setGenerator('app', generateApp),
plop.setGenerator('component', generateComponent)
}
Templates
Example; App skeleton - simple folders & files layout.
templates/
appTemplate/
| app/
| | public/
| | | favicon.ico
| | | index.html.hbs
| | | manifest.json.hbs
| | src/
| | | App.js.hbs
| | | index.css.hbs
| | | index.js.hbs
| | | routes.js.hbs
| | test/
| | package.json.hbs
| index.js
|
Generate an app skeleton
Prompts and actions
In actions you will see to get our app folder structure correct we have to replicate the type: 'add', rather than use the type: addMany which will add many files into one folder. There are many action types you can use.
You can also see the "name" variable has a modifier in front of it
pascalCase
you can find all modifiers here.
templates/appTemplate/index.js
module.exports = {
description: 'Generates new App folder',
prompts: [
{
// "input" standard inquirer prompt
type: "input",
// name is the variable you can reuse in your template files and path
name: "name",
// message is the prompt to the user aka. context
message: "What's the name of the new app?",
// optional input validation function
validate: function (value) {
let message = true
if (!/.+/.test(value)) {
message = console.error('Missing', 'you must define the app name')
} else if (value.length < 4) {
// send new message for user feedbaack
message = console.error(
'Too Short',
`"${value}" is not descriptive enough`,
)
}
return message
}
}
],
// TODO:: Make destination path an input in above prompts for user to choose where the component should live.
actions: function () {
return [
{
// action type
type: 'add',
// output path
path: '../../../apps/{{pascalCase name}}/package.json',
// input path
templateFile: './templates/appTemplate/package.json.hbs',
// the section of the path that should be excluded when adding files to the destination folder
base: 'app'
},
{
type: 'add',
path: '../../../apps/{{pascalCase name}}/src/App.js',
templateFile: './templates/appTemplate/src/App.js.hbs',
base: 'app'
},
{
type: 'add',
path: '../../../apps/{{pascalCase name}}/src/index.css',
templateFile: './templates/appTemplate/src/index.css.hbs',
base: 'app'
},
{
type: 'add',
path: '../../../apps/{{pascalCase name}}/src/index.js',
templateFile: './templates/appTemplate/src/index.js.hbs',
base: 'app'
},
{
type: 'add',
path: '../../../apps/{{pascalCase name}}/src/routes.js',
templateFile: './templates/appTemplate/src/routes.js.hbs',
base: 'app'
},
{
type: 'add',
path: '../../../apps/{{pascalCase name}}/public/favicon.ico',
templateFile: './templates/appTemplate/public/favicon.ico',
base: 'app'
},
{
type: 'add',
path: '../../../apps/{{pascalCase name}}/public/index.html',
templateFile: './templates/appTemplate/public/index.html.hbs',
base: 'app'
},
{
type: 'add',
path: '../../../apps/{{pascalCase name}}/public/manifest.json',
templateFile: './templates/appTemplate/public/manifest.json.hbs',
base: 'app'
},
]
}
}
index.js.hbs
import React, { StrictMode } from "react"
import ReactDOM from "react-dom/client"
import App from "./App"
import { BrowserRouter } from "react-router-dom"
import './index.css'
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
)
App.js.hbs
import React from 'react'
import Routes from './routes'
const App = () => <Routes />
export default App
routes.js.hbs
import { Routes, Route } from 'react-router-dom'
export default function {{pascalCase name}}AppRoutes() {
return (
<Routes>
<Route path="/" element={<Landing />}
<Route path="/tab-one" element={<TabOne />} />
<Route path="/tab-two" element={<TabTwo />} />
</Route>
</Routes>
)
}
index.css.hbs
This is a small preview of a main.css file showing that you can add your CSS as normal you could ask in your prompts for global styling named variables if you wanted to add them here.
body,
html {
height: 100%;
margin: 0;
padding: 0;
font-family: ...
}
manifest.json.hbs
This is a small preview of a manifest file showing how you can assign the name variable to keys in the JSON file.
{
"short_name": "{{name}}",
"name": "The Best {{name}}",
"icons": [],
...
}
Generate a file
Prompts and actions
templates/componentTemplate/index.js
module.exports = {
description: 'Generates new component',
prompts: [
{
type: 'input',
name: 'name',
message: "What's the name of the component? (make sure to suffix/prefix with ...)",
validate: function (value) {
let message = true
if (!/.+/.test(value)) {
message = console.error('Missing', 'you must define a component name')
} else if (value.length < 4) {
message = console.error(
'Too Short',
`"${value}" is not descriptive enough`,
)
}
return message
},
},
{
type: 'input',
name: 'app',
// useful for multi app codebase or in not multi just add src/pages/app or remove.
message: "What's the name of the app for this component to be added?",
validate: function (value) {
let message = true
if (!/.+/.test(value)) {
message = console.error('Missing', 'you must define a app name')
// TODO:: add some smarts, can a config file be used to check the existence of an app here?
} else if (value.length < 4) {
message = console.error(
'Too Short',
`"${value}" is not descriptive enough`,
)
}
return message
}
},
{
type: 'input',
name: 'location',
message: "Where inside the app src folder should this component live? (e.g. shared common folder)",
validate: function (value) {
let message = true
if (!/.+/.test(value)) {
message = console.error('Missing', 'you must define a folder location name')
// TODO:: add some smarts, can a config file be used to check the existence of an app here?
} else if (value.length < 4) {
message = console.error(
'Too Short',
`"${value}" is not descriptive enough`,
)
}
return message
}
}
],
// TODO:: make destination path an input above for user to choose where the component should live.
actions: function () {
return [
{
type: 'add',
path: '../../../apps/{{app}}/src/{{location}}/{{pascalCase name}}/index.js',
templateFile: './templates/componentTemplate/index.hbs',
}
]
}
}
index.js.hbs
import React from 'react'
// Add your other imports for this component here...
// Add types here if you are using .ts/.tsx
// NOTE: If you know the required props for this component you can destructure them and add them below insted of "props"
function {{pascalCase name}}(props) {
// Add functions as you need them...
// Add as usual add your component return jsx...
return (
<div className="">
<label>HW</label>
</div>
)
}
Some Pros & Cons of using Plop
Pros:
Very quick implementation gentle learning curve.
Easy to add new code to existing apps.
Easy to make adjustments to templates to keep aligned with any new conventions or updated patterns*.
You can also import non-template files like favicon.ico...
Cons:
* Because of its ease of use, it is simple to make adjustments to the templates that suit the developer on the fly. Docs and an opinionated implementation guide would need to be maintained.
Plop template files cannot be dynamically downloaded and used from a template repo. They can be published as a (private) npm module, git issue
Some final things to consider
Is code auto-gen a complete buy-in for all devs when creating new apps/folders/files?
Does this implementation replace manual creation altogether? Should it?
Where/How should manual code creation be managed when used side by side with automated code auto-gen?
How can conventions and code patterns be established for templates?
How can existing auto-generated code be updated when the code auto-gen template changes with an evolving codebase?
I recommend using code auto-generation for brand new projects or files, but only after establishing some foundations & opinions on how templating should be implemented and maintained.
It is a great way for any developer, especially freelancers or solo workers to free up some time so you can get down to the fun stuff.
Till next time ๐