This site runs best with JavaScript enabled.

Make your own DevTools


How creating DevTools specific for your own app can improve your productivity

Watch "Create App DevTools to enhance development productivity" on egghead.io

How many times have you entered the username/password on the local version of your app? How many times have you filled out that one form just so you could fix that bug that only shows up when some specific fields are filled in a specific way? How annoying is it to have to restart your dev server when you want to change which backend environments your frontend is hitting?

I don't know whether these things all apply to your situation at work, but these things were really annoying for me when I worked on an admin portal at Alianza. So I thought about creating a browser extension like I'd seen people do at previous jobs I had worked at. That way I could have the extension control the app/environment. But I remembered trying to work on and contribute to those and it was pretty indirect and not entirely straightforward. And there were some things I wanted to do only temporarily, and only for my own development, not the rest of the people using the extension. The other problem with an extension is that there are quite a few limitations to extensions and I didn't want to have to hack around those.

So instead, I had this idea to have some code that's actually in the app instead. Both in the repo, and actually running in the app, like the rest of the code in the app. Here's what it looked like (screen-grabbed from a demo video I made when I left the company):

Alianza Admin Portal with DevTools visible

That was an AngularJS app and I did a few fancy things to make that work. I've put together a relatively small demo React app (repo) to show you an example of how this could work. In this app we're using the DevTools to control a feature toggle, but remember that App DevTools enable you to do just about anything.

If you don't want to play around with it yourself, here's what that looks like:

Here's the basic idea:

1// src/index.js
2import loadDevTools from './dev-tools/load'
3import React from 'react'
4import ReactDOM from 'react-dom'
5import './index.css'
6import App from './App'
7
8// load and install the dev tools (if they need to be)
9// and when that's done, let's render the app
10// NOTE: if we don't need to install the devtools, then the callback
11// is called synchronously so there's no penalty for including this
12// in production.
13loadDevTools(() => {
14 ReactDOM.render(<App />, document.getElementById('root'))
15})

And then:

1// src/dev-tools/install.js
2function loadDevTools(callback) {
3 // this allows you to explicitly disable it in development for example
4 const explicitlyDisabled =
5 window.location.search.includes('dev-tools=false') ||
6 window.localStorage.getItem('dev-tools') === 'false'
7
8 const explicitlyEnabled =
9 window.location.search.includes('dev-tools=true') ||
10 window.localStorage.getItem('dev-tools') === 'true'
11
12 // we want it enabled by default everywhere but production and we also want
13 // to support the dev tools in production (to make us more productive triaging production issues).
14 // you can enable the DevTools via localStorage or the query string.
15 if (
16 !explicitlyDisabled &&
17 (process.env.NODE_ENV === 'development' || explicitlyEnabled)
18 ) {
19 // use a dynamic import so the dev-tools code isn't bundled with the regular
20 // app code so we don't worry about bundle size.
21 import('./dev-tools')
22 .then(devTools => devTools.install())
23 .finally(callback)
24 } else {
25 // if we don't need the DevTools, call the callback immediately.
26 callback()
27 }
28}
29
30export default loadDevTools

Here are a few key takeaways:

  1. The DevTools are available if we haven't explicitly disabled them and we're in development. Or if we're in production, we have to explicitly enable them.
  2. The code for the DevTools do NOT end up in our production bundle. They're chunked separately from the rest of the app. Whenever we're doing something to improve the developer experience productivity, we want to limit (or eliminate) the impact on the user experience (because users care about how you write code).
  3. We wait to render the app until the DevTools have been installed so it can make all the adjustments to the global environment before the app renders.
  4. If we don't need to install the DevTools, then we immediately call the callback so we can get to rendering our app as soon as possible (with the way this is coded, there's no penalty for including the DevTools code and doing this check to install the DevTools). For the same reason as in #2.

Once you get into the dev-tools.js file, you have free reign to do whatever you want (even async stuff). Just export an install function and you're off to the races:

1// src/dev-tools/dev-tools.js
2
3import React from 'react'
4
5function install() {
6 function DevTools() {
7 return <div>Hi from the DevTools</div>
8 }
9 // add dev tools UI to the page
10 const devToolsRoot = document.createElement('div')
11 document.body.appendChild(devToolsRoot)
12 ReactDOM.render(<DevTools />, devToolsRoot)
13}
14
15export {install}

You can check out the src/dev-tools/dev-tools.js file in the repo for an idea of what you can put in there.

Local DevTools

One thing I think is pretty neat and want to call out specifically is the concept of "local" DevTools. So, there could be things (functions, automations, etc.) that you'd like to have on your own machine, but nobody else wants those things or maybe you're in the middle of working on them. Whatever the case may be, having a script you can have run for you and you alone in your app can be really helpful. So in the App DevTools script we have something like this:

1// load local dev tools if it's there
2// NOTE: this is using some webpack-sepecific features.
3// if you're not using webpack, you might consider using
4// https://npm.im/preval.macro or https://npm.im/codegen.macro
5const requireDevToolsLocal = require.context(
6 './',
7 false,
8 /dev-tools\.local\.js/,
9)
10const local = requireDevToolsLocal.keys()[0]
11if (local) {
12 requireDevToolsLocal(local).default
13}

That basically says: Hey, if there's a file in src/dev-tools/dev-tools.local.js then go ahead and load it. Then you just put *.local.* in your .gitignore and voilà! You can now have a script that runs in your app whenever you load it up, but doesn't run for anyone else or even get committed to the repo.

Ideas

So aside from giving a great UI for enabling/disabling feature toggles, you can also listen to changes to the URL and automatically fill out forms (using the screen and async utilities from @testing-library/dom and the event utilities from @testing-library/user-event). You could also list out model data under forms to show the state of validation/etc. Also, the ability to change the backend I was hitting was hugely valuable.

The potential for automating your local development is endless. This gives you so much power.

Warning! Production !== local

With the power of App DevTools comes great responsibility. You need to take care that your User Experience doesn't take a back seat to your Developer Experience (one of those categories of people pay you money and the other costs you money)! That's why we take care in the way this is set up to not increase our bundle size or slow down our rendering time.

Also, please please please make sure you don't ship anything that won't work without the DevTools enabled! That can be surprisingly easy to do when you spend all of your time developing the app with the DevTools enabled. To avoid this, make sure that you have a good suite of tests that run with the DevTools disabled.

Conclusion

When I implemented App DevTools at Alianza, I was surprised to find that they were just as useful to Backend, QA, and Product folks as they were to me. The ability to have them loaded up and useful in production helped me quickly resolve production bugs. When done carefully, these can really enhance your productivity as well. I hope you give it a shot and let me know how it went. Good luck!

Play around with the app and repo.

Discuss on TwitterEdit post on GitHub

Share article
Kent C. Dodds

Kent C. Dodds is a JavaScript software engineer and teacher. He's taught hundreds of thousands of people how to make the world a better place with quality software development tools and practices. He lives with his wife and four kids in Utah.

Join the Newsletter



Kent C. Dodds