Roll Your Own Component Generators
Prerequisites
- Node.js installed (any version from this decade should do)
- A project where you’re making more than about 3 components (otherwise just type it out you muppet)
- Basic knowledge of template literals in JavaScript
fs/promises. If you’re still using callbacks for file operations in 2025 we need to have a chat- A terminal. Any terminal. Even the one at the airport if you’re desperate
- A llama. Optional. But strongly recommended for quality assurance purposes. Llamas have an innate sense for clean boilerplate
Writing quick scripts to generate boilerplate code is a handy technique that can save you tons of time. I recently used this while building a React library that needs a lot of components (a wizard interface), but you can apply this approach to generate any kind of repetitive code structure you need. There are probably way better libraries out there for code generation if you need something more robust, but sometimes writing a simple custom script is all you need. It’s quick to implement, adds no dependencies, and you have complete control over the templates. Perfect for those times when you need several similar components or files and can’t be bothered typing the same boilerplate for the fifteenth time today.
First thing to do is create a new file, you could use pretty much any language for this, bash would be quite good here, but for this we’ll use node.
# if you dont have a scripts directory already
mkdir -p scripts
# otherwise
touch scripts/generate-component.js
chmod +x scripts/generate-component.js
The actual script itself is fairly self explanatory, we use Javascripts string interpolation to create, well, whatever we want really. Anything that we want to end up in a file. I’m sure this technique has some fancy name, that probably includes the word templating in it.
For some React components and tests, lets do something like the following:
const componentTemplate = (name) => `import React from 'react';
interface ${name}Props {
}
const ${name} = ({}: ${name}Props) => {
return (
<div>
<h1>${name} Component</h1>
</div>
);
};
export default ${name};
`;
const testTemplate = (name) => `import { render, screen } from '@testing-library/react';
import ${name} from './${name}';
describe('${name}', () => {
it('${name} renders successfully', () => {
render(<${name} />);
expect(screen.getByText('${name} Component')).toBeInTheDocument();
});
});
`;
const indexTemplate = (name) => `export { default } from './${name}';
`;
Extend that example as much as you like. Maybe you want snapshots for every component, maybe every new component you make has a useEffect or wants some useState setting up, just add it to your template. Go nuts.
Next the actual function that writes this template to disk. Here we take the component name, and a default directory (I often have to split components into libraries, so just make that bit a parameter by default).
We use nodes mkdir to create a directory, and then join the path with a file name of our choice and the contents of our file.
const createComponent = async (name, directory = 'src/components') => {
try {
const componentDir = join(process.cwd(), directory, name);
await mkdir(componentDir, { recursive: true });
await writeFile(
join(componentDir, `${name}.tsx`),
componentTemplate(name)
);
await writeFile(
join(componentDir, `${name}.test.tsx`),
testTemplate(name)
);
await writeFile(
join(componentDir, 'index.ts'),
indexTemplate(name)
);
console.log(`Created component ${name} in ${componentDir}`);
} catch (error) {
console.error('Error creating component:', error);
}
};
We add a bit of error catching around the script, the whole thing looks something like this:
import { mkdir, writeFile } from 'fs/promises';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const componentTemplate = (name) => `import React from 'react';
interface ${name}Props {
// Define your props here
}
const ${name} = ({}: ${name}Props) => {
return (
<div>
<h1>${name} Component</h1>
</div>
);
};
export default ${name};
`;
const testTemplate = (name) => `import { render, screen } from '@testing-library/react';
import ${name} from './${name}';
describe('${name}', () => {
it('${name} renders successfully', () => {
render(<${name} />);
expect(screen.getByText('${name} Component')).toBeInTheDocument();
});
// Add more tests here
});
`;
const indexTemplate = (name) => `export { default } from './${name}';
`;
const createComponent = async (name, directory = 'src/components') => {
try {
const componentDir = join(process.cwd(), directory, name);
await mkdir(componentDir, { recursive: true });
await writeFile(
join(componentDir, `${name}.tsx`),
componentTemplate(name)
);
await writeFile(
join(componentDir, `${name}.test.tsx`),
testTemplate(name)
);
await writeFile(
join(componentDir, 'index.ts'),
indexTemplate(name)
);
console.log(`Created component ${name} in ${componentDir}`);
} catch (error) {
console.error('Error creating component:', error);
}
};
const args = process.argv.slice(2);
const componentName = args[0];
const directory = args[1];
if (!componentName) {
console.error('Please provide a component name');
process.exit(1);
}
createComponent(componentName, directory);
Lets add this now as a utility in our package.json
{
// ...the rest of the package.json. but dont copy this, because comments aren't allowed in json.
"scripts": {
"generate": "node scripts/generate-component.js"
}
}
Now you can run npm run generate Button <with-a-directory> and, everything going well, you should have a nicely generated component, with a test and everything.

To improve on this you could also do things like append the file name to the root index in wherever you are exporting the component from, the worlds your oyster at this point.
If you did, or didn’t enjoy reading this, let me know and I’ll find something more interesting to blog about. Personally I enjoy learning about little tips and tricks like this, I used the time I saved to write this article. Until next time.