I find it helpful to stick to a few guiding principles while working on front-end code. Here are my thoughts on how to keep your project clean, scalable, and team-friendly.
Hey, friends đ
I thought it might be helpful to provide some information on principles I follow when creating a new project and why I believe they are essential in creating clean and scalable code that works well when working individually or as a team.
Forewarning, most of the content in this article will be React focused (both native and web), but you might find it helpful as well. While the content in this post is comprised of my opinions and preferences, I do not believe that you must follow them in order to be a good developer.
Truthfully, I believe that any good developer should be able to use the tools that their team prefers and write good code regardless of the library framework, state management package, or styling library in use.
Linting đľď¸
I strongly believe that one of the key indicators of a well-established project is a good ESLint setup. ESLint is a utility that analyzes your code and identifies any problems as you write. This is helpful for a number of reasons, but one, in particular, is that any contributors to the project will be required to adhere to a strict set of guiding principles.
Rules in ESLint can range from import order (weâll talk about that later) to much more niche things like disallowing hardcoded content or disallowing nested ternary expressions. For example, if one of your project’s core principles is accessibility (a11y) and internationalization (i18n), then youâre likely going to have ESLint rules requiring the use of translated content and a11y attributes. These rules will ensure that all contributors in the future follow the same guiding principles, thus creating a more uniform and scaleable application.
Why would you want these rules that make it more difficult to add collaborators?
Simply put, your team has a vision for your product. Youâve likely all aligned on what you believe defines good code and what principles you want contributors to follow (such as internationalization). Enabling these rules means that any contributions to your project will follow those same principles and not take away from your teamâs effort to create a project that youâve worked hard on.
What if I donât like some of the rules?
You and your team might not always want to use all of the rules from ESLint, in which case you can disable rules your team disagrees with. But, it is worth recognizing that the rules created are there for a reason; theyâve been identified as widely accepted by a large community of developers. Learn more about the core rule guidelines.
You will find an example of my ESLint Config below, which primarily utilizes the @shopify/eslint-plugin
. Iâve included comments above the two rules which I find myself customizing the most often.
Formatting đ§š
Piggy-backing ESLint, I find it important to ensure that members of your team are using the same formatting tools as well. This helps reduce your PRs from becoming cluttered with formatting changes, e.g. spacing, commas, bracket changes, etc. I believe itâs mostly common practice now to use Prettier for this.
Side note, Iâve noticed that some folks might have trouble with their Prettier extension picking up the config file for a project, in which case you can usually inspect the output in your editor (â | Ctrl) + Shift + U
to open the Output window pane in VS Code. Then, you can select the Prettier output task and see if there is an error with your Prettier configuration. If there are no errors, you can use (â | Ctrl) + Shift + P
to open the Command Palette in VS Code and type Format document
then select Format document with âŚ
followed by selecting Prettier. This will usually get Prettier to function correctly again. Most frequently, I find myself using the Prettier config from Shopify, @shopify/prettier-config
Sometimes, depending on the project, I will customize this a bit and extend some additional rules. If Iâm working on a project that uses Tailwind CSS (more on that further down), Iâll also utilize the extension. This ensures that all class name definitions follow the Tailwind recommended class sort order (base layer, components, utilities).
Unit tests đ§Ş
A pretty fundamental piece of creating a project that is clean and scaleable is writing unit tests. Unit tests can be written for a wide variety of reasons, and often help us identify errors that we wouldnât have caught otherwise. Packages such as React Testing Library provide lightweight utility functions that allow us to write better, more maintainable tests that track the functionality of our components and their interoperability.
Ideally, your tests should be focused on how your software is used. This is why I tend to utilize React Testing Library (RTL), as it tends to focus primarily on the way that a user would use your application. For example, they might find a specific form and enter specific data in this form field. What is the expected behavior when they input only one of the three required fields and then press âSubmitâ? From the React Testing Library documentation:
This library encourages your applications to be more accessible and allows you to get your tests closer to using your components the way a user will, which allows your tests to give you more confidence that your application will work when a real user uses it.
Continuous Integration (CI) đ
I view CI as the glue of your project. Itâs what you leverage to ensure that all contributions align with the principles and rules youâve configured for your project. Itâs how contributors are identified of whether or not their code is acceptable, whether theyâve broken types, broken implementation of a component, or otherwise written code that doesnât comply with your projectâs prettier configuration. Utilizing the scripts in your project, you can ensure that all contributions are clean, tested, and type-safe.
GitHub makes this easy simply by adding workflows to your projectâs .github/workflows
directory. The workflows are comprised of a .yaml
file with scripts that run against the contributed code to ensure the contributions pass the checks youâve put in place. Iâve attached a sample workflow below which I use to ensure that contributions match and meet the criteria for a project.
You can add additional workflows to this as well which allow you to view the changes in Storybook (if youâre using Storybook â highly recommended), overview documentation changes (if youâre using something like Nextra and the site is hosted on Vercel youâll get a preview link for the website in the PR), and whatever else you might find useful for accepting and reviewing contributions.
Imports
There are a few different types of imports to know when thinking about your exports and imports. Primarily, those are Absolute, Barrels, and Relative. Absolute imports are typically looked at as being easier to read and maintain in projects. Relative imports are imports that are defined relative to the current fileâs location in the project. Itâs quite common to see teams using absolute imports in practice as relative imports can become quite cumbersome to manage as the shape of the application changes over its lifetime.
What about barrels? Barrels are when you roll up and export a list of exports from a single, convenient location. Consider the root index.ts file of a package that youâre using. That package likely exports individual components at the root of the package (it also might be using paths in package.json for directing consumers to the file location). For example, I tend to prefer exporting the contents of a component directory (e.g. components/Button) via a barrel. This allows me to export the component, the style, the types, and anything else from that directory.
At the end of the day, this really comes down to team preference. However, you should note the risks when using barrels. In particular, there is a risk of creating circular dependencies. For example, if Select.tsx
relies on the usage of a hook, but Form.tsx
also relies on that same hook you will end up with a circular dependency being created by Select.tsx
Iâve found over time (I used to export entire directories), that exporting an entire directory such as your components
leads to a greater risk of creating circular dependencies. These can be quite easy to identify but can be a bear to resolve. Resolving circular dependencies often requires you to rethink how your application is structured. Additionally, both absolute paths and barrels (exclusive) can cause issues for Jest and some bundlers.
If youâd like to read more about barrels and their risks, I recommend reading Are TypeScript Barrel Files an Anti-pattern?
Bundlers đŚ
This comes down to which React framework youâre using (e.g. Next, Remix, etc.) and what youâre building, e.g. are you building a package or are you building a website? There are a lot of emerging technologies in bundlers and compilers. For example, there are bundlers like SWC Pack, Vite, and Webpack. You might also see tools like esbuild, rollup, swc, or tsup, or you might even set Vite being used in library mode.
I think this really just comes down to personal preference. At the time of writing this, Rust is the big winner in the world of performance. That being said, SWC Pack is still in development (at the time of writing this), so it might not be the best choice for building a production platform. However, SWC in itself should be sufficient if you are building a library. My personal preference when I canât use SWC is to use tsup as Iâve found that it is still incredibly quick, in my tests with the @shopify/blockchain-components
repository we saw our bundle times (including type declarations, tree-shaking, and minification) drop from ~12 seconds using Vite to ~3 seconds using tsup.
Styling libraries đ¨
This is probably one of the areas where folks have the biggest opinions or hot takes. However, in my opinion, you should be comfortable using whatever the team prefers. Whether that is Tailwind CSS, Styled Components, or Vanilla Extract, pick up what the team prefers and get to shipping đ˘
My preference, however, is that Tailwind has a well-composed collection of classes that can help you create a beautiful product without having to write a single line of CSS. However, I do find that Tailwind projects can be quite complex to maintain if the team has not taken steps to minify the amount of className
declaration for each individual component. It can also be quite a mess if youâre not using the prettier plugin from Tailwind because you might find that folks are duplicating or overwriting classes in the className
property because of how long the className
might be.
My go-to preference for working in a team setting is typically Styled Components or Vanilla Extract because I feel that it makes it a bit more straightforward to modify classes and see what exactly a particular style has attributed to it. For example, overwriting the background style would simply be a change to the background-color
, whereas with Tailwind you might find that the package is using a prefix and the class is ${prefix}-bg-neutral-500
. But, you might miss that there is a background-color
class in the className
already because it has a prefix and isnât immediately clear to you. Tailwind can also be a bit more of an on-ramp to your project in comparison to more straightforward styling packages. For example, a contributor with little to no Tailwind experience bg-neutral-500
does not immediately transfer to a color value and might require looking in the Tailwind config or the Tailwind website to find the color value.
In comparison, tools like Styled Components also have downsides. For example, the use of createGlobalStyle
to create a global stylesheet that gets injected at runtime might create overlapping styles which can make it quite difficult to balance styles and keep them relative to your application. There are ways around this, such as creating a shared Text component that defines the styles for all of your text elements. Needless to say, I think the three of them are all great options, but each with its pros and cons to be aware of before fully embarking on your development journey.
Tools
There is a multitude of tools available that I find to make the development of a project much simpler. Additionally, I have some tools on my website here that you should check out! Check out the color palette tool and the gradient generator. Some other things I find of use are a good collection of git aliases, hereâs an example of useful aliases. Some other things I find quite helpful are making use of VS Code snippets to generate boilerplates for new components, APIs, hooks, etc.
Here is a list including some of my favorite tools â
Until Next Time đ
Feel free to message me on Twitter and let me know what you thought about the article, or share some insight as to what some of your practices are.
If you learned something here or have something that you think I could learn from as well, share it with me!