I have been contemplating writing my first—actual—blog post for quite some time now, but there’s just something about writing a first blog post that makes me postpone the task endlessly. The struggle is undeniably real. I’ve ‘mentally’ written about a hundred posts—some drafted, others deleted—but not a single one published.
Today, I decided to finally suck it up and write it. So here goes.
It’s Sunday evening as I write this, and I’m sitting at a cafe. Hopefully, this change in environment will do the trick, and by the end of the day I will have published this post.
Anyway, back to the topic. A while ago, I wrote a post on X about how reading open-source code is an underused method to upskill. I used the Sentry codebase as an example, an excellent starting point for React.
How about we put it into action? Let’s dive into the Sentry codebase and see what lessons are in store.
The purpose of this exercise to learn as much as possible. It’s fine to miss details, or not understand everything fully. The goal is to explore the codebase, make smart observations, and take notes. If something seems complicated, we can always label it as a “black box” and come back to it later. Think of it as exploring a forest as opposed to climbing a mountain peak—we don’t have any specific destination.
I encourage you to follow along and make your notes; because learning by doing, remember?
I’m going to clone the repository because I like to tinker with the codebase and do random analysis (you’ll see). Alternatively, you can also use GitHub to read the codebase. GitHub has decent support for reading code with their definition/reference lookups. So, let’s get started.
First, I like to find out how big the codebase is.
1git ls-files | wc -l213270
That’s a hefty 13k files. Luckily, we are only interested in the React parts of it, so we can narrow it down. Sentry is a mono repo of both backend and frontend code with a mix of Python/Django, Rust, and TypeScript. Any guesses on where the React code is hidden?
It’s under a folder called static
. The webpack.config.ts
at the root of the project is configured to bundle from that folder, with static/index.ejs
serving as the main HTML template (for HTMLWebpackPlugin). Also, if we ignore all the assets and “less” files, all the TypeScript code is under static/app
. Let’s see the file count there.
1cd static/app2git ls-files | wc -l34532
A much more manageable number.
The Folder Structure
We have a bunch of top-level directories and files. As an exercise, take a guess—just by looking at the name—what each file or directory might contain. For now, ignore the .spec.ts
files; those are just unit tests and don’t “do anything” of their own.
Though looking at a spec file can be a good way to understand what the corresponding file or module is “supposed” to do.
Done guessing? Here’s what I guessed.
- actionCreators - utils for creating redux actions
- bootstrap - startup code, perhaps?
- chartcuterie - A pun on “charts” and “charcuterie”, so something related to charts?
- components - reusable React components
- constants - Redux-related or other constants
- data - text/json data
- gettingStartedDocs - docs content
- icons - icon files, duh
- plugins - plugins, not sure for what
- stores - redux stores
- stories - no clue
- styles - css styling
- types - TypeScript types which get reused
- utils - utility functions
- views - React components not meant for reuse
- api.tsx - setup code for calling backend APIs
- index.tsx - entry point for the bundle
- locale.tsx - translation/locale related setup
- main.tsx - the main React component
- routes.tsx - React router setup
- sentryTypes.tsx - more TypeScript types?
- utils.tsx - more utils or some kind of setup for utils
Now let’s dig into these one by one to quickly find out how well we guessed.
Why guess instead of just finding out what’s inside right away? It’s an exercise. The hope is that we can improve our guessing skills by doing this more often. Also, it creates a good excuse to purposefully look through the folders and files.
actionCreators
Opening a random file here (actionCreators/dashboards.tsx
), my guess was off. There’s no Redux here; these are just wrapper functions around the API Client (imported from static/app/api.tsx
).
The general format of these functions is:
- Take the inputs
- Pass inputs to the API client correctly using the right method, query, body, and so on.
- Handle any error responses.
- Return a promise.
Sure enough, opening another bunch of files in the actionCreators
folder, we see the same overall theme.
api.tsx
Since the previous folder referenced this file frequently. It’s natural to look at this next.
And damn, this file seems to be import and handle a lot of things. It’s not as easy to parse as the files in the last folder, but we can handle it. Let’s scroll through, read symbol names and get an idea of what’s happening.
If you are on GitHub, just use the Symbols panel to the right and click on the symbols to hop to them.
I see names like: isSimilarOrigin
, csrf
, buildRequestUrl
, indicating code for for making API calls, handling responses and errors. api.tsx
seems to be the ideal place for it.
Most of the action in this file happens in the request
function under the Client
class, which can be summarized as:
- Build the request url.
- Package the body payload.
- Do some “metrics” stuff using
sentry/utils/analytics
- Make the API call using
fetch
. - Handle success/errors and if something seems off, use Sentry (how meta) to capture exceptions for the devs to analyze.
This file gives us good insight into the general structure of most of Sentry’s backend APIs. There’s also a random hasProjectBeenRenamed
function and I love it. It’s messy, but gets the job done.
In the Sentry Development Philosophy—a good read btw—they have a section called “Embrace The Duct Tape”. This function could be an example of such a duct tape solution.
stores
Looking inside stores
, we do find a bunch of store files, as expected. However, they aren’t Redux stores! Sentry uses Reflux and not Redux, so that was a bit presumptuous of me.
As I’m not really familiar with Reflux, I had to read through their GitHub docs to understand what’s happening. The concept is pretty similar: components call actions, actions mutate stores, and store updates trigger the relevant components to re-render.
For instance, open the alertStore.tsx
.
The storeConfig
defines the store, and also has some helper functions for mutating the store. The alertStore
is really straightforward: the state is simply an array of alerts, and a count tracking the number of alerts.
Each alert has the type:
1type Alert = {2 message: React.ReactNode;3 type: keyof Theme['alert'];4 expireAfter?: number;5 id?: string;6 key?: number;7 neverExpire?: boolean;8 noDuplicates?: boolean;9 onClose?: () => void;10 opaque?: boolean;11 url?: string;12};
The only actions are addAlert
and closeAlert
. addAlert
updates the count and uses it to set the key
of the new alert. closeAlert
removes an alert from the store. Additionally, addAlert
has some logic to automatically close an alert if expireAfter
is used.
The store also has logic to handle “muted alerts” which uses local storage.
Overall, a cleanly written store that’s easy to understand. Heck, just reading the code paints a good picture of what’s happening, even for someone who has never used Sentry.
Other stores, like the projectsStore.tsx
are more complex. The projects store utilizes the API client to refresh organization details when a new project is created, but the main structure is the same.
One thing that stands out for me, is the use of “side effects” like local storage and API calls. In Redux, having side effects inside reducers is discouraged, but Reflux allows actions to mutate the state and cause side effects. I like it.
styles
So styles
doesn’t house any CSS files. Instead, there are a few TypeScript files which use @emotion/react to export styles. Also, since there are so few style files, it’s reasonable to assume that that most of styling is written directly in the components
or views
files—or, remember the static/less
folder we saw previously?
components and views
components
has a ton of React components! And all of them look like they get reused. Accordions, badges, banners, buttons, checkboxes, panels, and so on.
On the flip side, views
has React components designed for single-use. These are thoughtfully named and organized, making it easy to imagine what role they would play in the Sentry app.
For instance, consider the app/alertMessage.tsx
component, a view
that gets used inside the app/systemAlerts.tsx
view, and internally uses the Alert
component from components/alert
.
utils and utils.tsx
The utils
is folder is truly impressive. All complex logic that gets reused or can potentially get reused ends up in the utils
folder.
If we check the number of lines in the whole React codebase:
1find -type f | xargs wc -l | tail -12 247730 total
There are a total of 240k lines. Can you guess how much percentage of this code is inside the utils
folder?
1cd utils2find -type f | xargs wc -l | tail -13 120803 total
That’s right! About 50% of the code is inside the utils
folder.
There are so many single function utils files, like utils/getDynamicText.tsx
that has a single, small function which gets used in around 250 places. Each util
file imports it’s own dependencies and maintaining lots of small files ensures there are no circular dependencies.
Some util files are REALLY big, like the discover/eventView.tsx
file which has 1538 lines (though most of this code is inside the huge EventView
class).
The utils.tsx
file at the root is a small file with a collection of generic low-level utilities like arrayIsEqual
and valueIsEqual
—and some other random utils.
Digging Deeper
At this point, we have a decent understanding of various parts of the codebase, which means, we can dissect any flow in the Sentry app.
If you haven’t already, create a free account on Sentry, or use their sandbox environment: https://try.sentry-demo.com/
Try it out. Open the Sentry app and play around with it. Start with something simple—a basic UI component like a button or a badge and look through the code for it; then make it more complex gradually.
Identify flows that you find interesting—maybe something that’s similar to what you’ve worked on previously.
Rinse and repeat.
Keep guessing. Resist the urge to read the code rightaway. After you use a flow in the app, try to imagine how you would implement it, then look at the code to see how well you did.
Parting thoughts
If you followed through the whole exercise, I encourage you to compile your list of insights gained from this exercise. Everyone has their takeaways and there’s no right or wrong—your takeaways could be very different than mine.
Also, remember that there’s no minimum skill requirement here. It’s an exercise, and you improve with repetition. Make observations, look up stuff, take notes, identify areas that you don’t understand, and either skip them or come back to them later.
There are so many good open-source codebases, you can repeat this exercise with more of them; there’s always a lot to learn.
Thanks for reading! If you liked reading this, let me know and I might do more such posts. Got some feedback? Feel free to reach out to me on X or via email.