Skip to content
bits&pieces by pekala

Frontend Development Environment as a Package

Or When to Build Your Own CRA

Published on May 3rd, 2017 - originally @ Hackernoon
~10 min read

The growth of JavaScript tooling ecosystem in recent years has been astonishing. The number of available tools exploded and the complexity of a setup required to bootstrap a project which would be considered modern has skyrocketed. This, as well as the quick rate of churn of these tools contributed largely to the famous JavaScript fatigue. The frustration fuelled a number of projects that aim at abstracting away that complexity, usually in a form of zero-config development setups. Most notable examples are nwb and create-react-app but the list is growing.

These projects usually help developers in two ways:

I recently got interested in exploring the latter, let’s call it Development Environment as a Package (it’s a mouthful, so let’s use πŸ’»πŸžπŸ“¦ for short). The idea is pretty simple. Take all the infrastructure the sits around the code of your application and extract it into a single installable package that your project depends on. Just yarn add my-dev-env -D and you’re done. While this seems plausible, let’s first explore why it might not be a good idea.

Why you shouldn’t use πŸ’»πŸžπŸ“¦

Any time you extract a piece of your project or it’s dependencies and build an abstraction around it, you introduce an indirection, place the extracted code in a black box. This indirection will inevitably need to be unboxed once the limitations or bugs in the extracted code start to leak. Breaking the abstraction to peak inside can get messy and frustrating. It is especially so, when the process involves complex tools with elaborate configurations, like in the case of modern frontend tooling (ESlint, Webpack, etc.).

Practical example You’re getting cryptic test runner errors for your unit tests suite. In a traditional setup you can debug by playing with the test runner configuration in the root of the project, but with πŸ’»πŸžπŸ“¦ you will have to yarn link the dev env package first or make the changes somewhere in thenode_modules directory.

Building wrappers around lower level tools leads to a loss of access to some of their features. This gets especially limiting when the tools introduce new features and your wrapper prevents you from easily accessing them.

Practical example A new version of webpack introduces optimisations to bundle size, provided you add a plugin to your configuration. With a traditional setup, you can simply upgrade webpack and paste the plugin configuration to your webpack.config.js but with πŸ’»πŸžπŸ“¦ you will need to go through the process of updating the external dependency (which may be 3rd party).

Lastly, most tools are optimised for usage directly in your project, and extracting them to a package can require a bit of hoops jumping and cringeworthy hacks. This adds new layers of complexity to your setup you wouldn’t otherwise need.

While these are undeniable drawbacks of the πŸ’»πŸžπŸ“¦ approach, they didn’t prevent the projects like create-react-app from becoming very popular.

Why you should use πŸ’»πŸžπŸ“¦

Some obvious wins include significant reduction in the time and effort required to start a new project as well as convenient upgrades of the toolchain, especially across multiple projects. It’s also much easier to focus on authoring code that adds real value to your enduser, when you don’t have to spend hours trying to get different tools work together. Outsourcing the development environment setup can be a godsend for all developers that find the frontend ops tasks challenging, tedious or dull.

Practical example A frontend development environment consisting of development server, linting, testing and build pipeline can easily have 50 development dependencies and 10 configuration files. By using e.g. CRA you will end up with one development dependency and no configuration files.

But the positive impact of using πŸ’»πŸžπŸ“¦ goes beyond time saving and cleaner directory listing. In my experience, it can encourage a more decoupled architecture for large scale projects. By allowing effortless bootstrapping and streamlined updates, it helps making the decision to split codebases. In consequence, it also encourages continuous improvement through experimenting with new tools and approaches, which can be easily back-ported to older projects or discarded.

Practical example When starting to work on a new projects a team opts out of using the πŸ’»πŸžπŸ“¦ used in all other projects in a the company. They do so in order to try Jest as the unit test runner instead of Mocha which is used in the current setup. After a while they make a decision to migrate to Jest fullyβ€Šβ€”β€Šthey install Jest in their πŸ’»πŸžπŸ“¦, release a new version, upgrade it in all other projects and use codemods to migrate the tests.

From an even more holistic perspective, choosing the πŸ’»πŸžπŸ“¦ approach instead of separate setups for each project can have positive effects beyond codebases. In companies with product teams working on multiple frontend projects, the introduction of πŸ’»πŸžπŸ“¦ makes it easier for developers to contribute cross-team or switch teams and for projects to change ownership. It also reduces the overall time wasted on solving the same problems in a slightly different way by different teams.

Practical example At the company I work for, Issuu, we have 5 product teams, each maintaining several frontend projects. A while ago, each of these projects had a similar, yet slightly different development environment. We started the migration to a πŸ’»πŸžπŸ“¦ by first trying the approach in one of the teams when starting a new product. Today we have around 10 projects across 3 teams that use it, and plan to migrate more soon. We love how improvements implemented by one team can be used across all these project with no additional effort.

As mentioned, there is a wide selection of open source πŸ’»πŸžπŸ“¦ solutions out there. However, I’ve decided to experiment with writing my own, which gave me some idea about pros and cons of this approach. Let’s start with the cons.

Why you shouldn’t write your own πŸ’»πŸžπŸ“¦

If the reason you’re considering building a CRA clone is the NIH syndrome then it would probably be more beneficial for you and the community to spend your time contributing to one of the existing projects instead.

By sticking to the established and battle-tested solutions you and your team will avoid a range of problems that other developers has encountered and spent hours solving. You will also be able to easily keep up with the evolving community best standards without the crippling FOMO.

Practical example Some time ago it turned out that the way some of the πŸ’»πŸžπŸ“¦ like preach-cli handle development SSL certificates in an unsafe way. The vulnerability was quickly fixed by the communities behind the projects.

When making a decision to opt out of using one of the existing dev environments supported by community you should also take into consideration the size of the team and the number of projects involved. In my experience this approach makes most sense in an environment where a significant number of frontend projects are initiated and need to be maintained.

It should also be mentioned that writing a robust πŸ’»πŸžπŸ“¦ is not an easy task, especially if you fancy advanced features like ejecting (i.e. an option to copy all the boilerplate into the project).

Regardless of these points, I found that an in-house solution worked well for my use case. Here is why.

Why you should write your own πŸ’»πŸžπŸ“¦

Composing your own development environment gives you the ability to fine tune it to the specific needs that you and your team might have. You don’t have to make trade-offs that can sometimes be difficult or simply not acceptable. You can bring only the tools you actually need and configure them the way you prefer. Development setup is a very opinionated domain, so conforming to all decisions made by an off-the-shelf solution can be challenging.

Practical example A lot of developers refrained from choosing CRA because of the lack of support for CSS preprocessors like Sass as well as server rendering of the markup. Furthermore, since you are in charge, you can make quick iterations and fix issues and introduce features, without the need of community approval.

The experience and understanding of the tooling ecosystem gained while composing your own πŸ’»πŸžπŸ“¦ is invaluable. Choosing the right tools and making them all play together as an installable package requires a deep understanding of their features, tradeoffs and configuration.

Practical example When choosing a JavaScript bundler for my πŸ’»πŸžπŸ“¦, I learned that rollup works great for libraries and widgets because of the clean output and easy module type control, while for products like web-apps webpack does an amazing job, thanks to e.g. robust code splitting.

Finally, when building an in-house πŸ’»πŸžπŸ“¦ you can usually afford to focus on DX (Developer Experience) much more than for a one-off development setup, making the life of your colleagues or your own easier. You can optimise the most common tasks and workflows, and polish the CLIs with e.g. readable output and autocompletion.

Practical example This kind of care for DX is exemplified when running CRA dev server on the port that is already used by another processβ€Šβ€”β€Šit will automatically choose an alternative one. Sweet!

Example of a simple πŸ’»πŸžπŸ“¦

While the codebase for projects like CRA can be a bit overwhelming, building a simple πŸ’»πŸžπŸ“¦ does not have to be difficult. Some time ago I gave a lightning talk on this subject at a CopenhagenJS meetup and to aid the talk I’ve published a simple example of a πŸ’»πŸžπŸ“¦ for authoring JavaScript libraries, with the code on GitHub. You can try it by running yarn add cphjs-inst-dev-env -D and then using one of the three available commands: yarn cphjs-tdd yarn cphjs-test and yarn cphjs-build. With this one development dependency you get bundling with Rollup with Babel compilation, support for Sass with SCSS syntax and importing from JS, linting with ESLint with Prettier auto-fixing, and a TDD setup with Jest for running tests on the compiled bundle.

I’ve used the development environment as a package approach for my side-projects (with CRA) as well as at work (with an in-house solution) for a while now. I’m quite happy with it, but I’m eager to hear about your opinion. What do you think?

Big thanks to Kenneth Skovhus and Mads Hartmann for reviewing this post.

Brought to you by Maciek . Who dat?

You might also enjoy

Picture of a fish from an old botanical atlas
/pieces/ Discovering Patterns with React Hooks
Drawing of a physics experiment with three test tubes, from an old science book
/pieces/ Integration Tests in Redux Apps