I came across Kent C. Dodds’ work on
babel-plugin-codegen and Babel macros some time ago on the Twitterverse and thought it was cool, but didn’t have a use case at the time. I tried to wedge it somewhere in the back of my memory for future use. That time came over a year later and I could say that memory piped up and said, “Ben, you could use that
babel-plugin-codegen right now”. However that didn’t happen. Instead I saw another tweet about
When my friend, let’s call him Jake, (because that’s his name) and I started a new project at work we came up with a new file naming convention for React / Redux components. Jake had become a little bothered by what he had seen on projects he had worked on and had come up with an alternative. I added my two cents worth and we finally reached upon the following:
Redux / React component example
Inside the <component>.connect.js
The <component>.connect.js file contains the functions
mapDispatchToProps needed for the
connect function. In the case above there was no need for
mapStateToProps. It then default-exports the result of calling
index.js for a React / Redux component
React component example (without react-redux connect)
index.js for a React component (without react-redux connect)
The component index.js file acts a glue between a component and the
connect function exporting the result, or it just exports the component if there is no need for Redux. In the case where there is no Redux it might be tempting to make any consuming component import the component directly. However importing components in the same way was preferred over a little bit of boilerplate code. So regardless of whether a component required Redux or not, the import was the same as you can see below:
If we had taken the approach to not include an index.js for a component without Redux we would have the following, which means you would need to know whether a component was Redux connected or not before importing it.
Separating a component file structure into *.component.js, *.connect.js, *.styles.js and index.js made for a clear separation of concerns but also aided searching for files in the browser and in the IDE.
You said something about code generation, where is it then?
When we agreed upon this approach to component file structure we did comment on the index.js file being just boilerplate code. Once set up you probably won’t need to revisit it often, only maybe when you realise you need to hook it up to the Redux store or you need to detach from Redux as part of some refactoring. So it would be useful if you could auto-generate the relevant imports and export, and regenerate it any time you added/removed the *.connect.js file. This is where
babel-plugin-codegen comes in.
The plugin runs at build time (in my case via
babel-loader), generating a string of code from code and injecting it where you specified. The following shows the before and after for a component index.js file:
By using the
/* codegen */ comment the plugin knows to execute the code in generateComponentIndex.js at build time passing in the Node.js value of
__dirname (directory name of the current module), and finally injecting the return value of generateComponentIndex.js in place of the import statement containing
/* codegen */.
generateComponentIndex.js uses the Node.js File System (fs) module for getting a list of files for the
directory passed in as an argument. In our case this will be the directory for whichever component is being evaluated by
babel-plugin-codegen you can use any Node.js module that runs synchronously.
To demonstrate it working I’ve built a task list. Yes, this is just another to-do list but I like the words task list because to-do just sounds like you have to do them but a task list, well it doesn’t sound like it’s forcing you do it. You can find the links to the repo and the working task list example at the end.
Caveat — if you refactor a component to either add Redux (adding a *.connect.js file) or remove Redux (removing a *.connect.js file), in order for
webpack-dev-sever to pick up your changes you need to edit the index.js file to trigger a rebuild. I add an additional space character usually. When using
webpack to produce a production build this isn’t an issue because it will execute any
babel-plugin-codegen code every time.
Does it impact build time?
I did an experiment. Well, sort of. I created two React projects each with one hundred very basic components, one using
babel-plugin-codegen and one without. For each project I recorded how long it took to start
webpack-dev-server straight after installing fresh dependencies (cold start), how long it took to start
webpack-dev-server after a previous start (warm start), and finally how long it took
webpack-dev-server to recompile when a component’s index.js file had changed. I repeated these tasks 5 times and took a “scientific” average.
The project not using
babel-plugin-codegen performed slightly quicker for each task on average. For the cold start test the project not using
babel-plugin-codegen was 0.1758 seconds faster than the project using
babel-plugin-codegen, 0.0171 seconds faster for the warm start test, and 0.015 seconds quicker for the individual file change test.
On previous runs of the experiment sometimes the project using the plugin performed slightly better.
Further use cases
As stated on the
babel-plugin-codegen README, “The applications of this plugin are wide”. In my example I have also used it to enforce filename conventions of each component related file, making sure each filename began with a lowercase character.
With this extra snippet of code, webpack will fail to compile if it catches an error (in the case below I changed addTask.component.js to AddTask.component.js).
babel-plugin-codegen is very powerful. Used sparingly, it could help in many use cases. But used too broadly could result in code that is harder to understand. Good documentation in the form of comments greatly helps here. While our use-case might not be perfect, it helped us to reduce some rarely visited boilerplate code as well as giving us the excuse to try out something new-and-cool.
To find out more about
babel-plugin-codegen head over to https://github.com/kentcdodds/babel-plugin-codegen and also check out https://github.com/kentcdodds/babel-plugin-macros
The code samples referenced above (and the Task list) can be found here https://github.com/mrbenhowl/auto-gen-boilerplate-code-babel-macro-codegen and here https://mrbenhowl.github.io/auto-gen-boilerplate-code-babel-macro-codegen/.
PS — If you don’t already follow Kent on Twitter (https://twitter.com/kentcdodds) I highly recommend you do, for his fantastic work and that he seems like a overall nice guy.