“Customizing” create-react-app

I’m using create-react-app lately, in an effort to simplify. Until now I’ve been using custom npm scripts, but it’s getting complicated. Create-react-app is very limited, but embracing constraints can be liberating so I decided to give it a try. I encourage you to try it too, and to stick with it as long as possible.

Sometimes you really do want to do something unsupported. You could eject and thus have total control, but I suggest you avoid this unless absolutely necessary. Create-react-app will continue to improve and you lose the ability to upgrade if you eject. There is also the issue of having more control—to the detriment of your time if you’re tempted to use it. Thus far I have avoided ejecting and any sacrifices are repaid in focus.

In those cases where you must do something unsupported, there may be an easy workaround. I wanted a customized Bootstrap but I also wanted it to be fully integrated into the build process so as not to have to download it each time a change is needed. A quick search confirmed that the create-react-app maintainer does not plan to include support for less, but he did offer a smart workaround using tools already at our disposal. In a gist:

  1. Create a new package that depends on official Bootstrap package
  2. Add a .less file which imports and customizes Bootstrap
  3. Add a build script to compile to css
  4. Depend on this package in your create-react-app based project

This is the route I took. It’s not perfect but it was easy to setup and gets the job done. My point is to encourage you to look for sensible workarounds before resorting to ejecting. To that end I’ve included an example of adding Bootstrap to your create-react-app based project below.


Create custom Bootstrap package

$ cd example-react-app
$ mkdir custom-bootstrap
$ cd custom-bootstrap
$ npm init # (or yarn init)

During init use build/index.css as the value for entry point. This will allow the CSS to be imported without knowledge of the internal location of the file in this module.

For the purpose of this article the module exists in the parent folder of the create-react-app project, and is therefore tracked in the same repository. If it will be a shared dependency that will be used in multiple projects you may want to place it a level up and use a separate repository. The instructions that follow will only change in the relative paths used.

Add Dependencies

Bootstrap, of course, but also less, which will be used to compile custom css.

$ npm install --save bootstrap less # or yarn add bootstrap less

Add a .less file (e.g. index.less)

// the main bootstrap file
@import "./node_modules/bootstrap/less/bootstrap";
// necessary to avoid compilation error
@icon-font-path: "../node_modules/bootstrap/fonts/";
// customizations
@font-size-base: 16px;
@line-height-base: 1.7;

In my simple setup I left the glyphicons in their original folder within the Bootstrap module, so it is necessary to give the new relative location with the @icon-font-path variable.

Add a build script in package.json

  "scripts": {
"build": "lessc index.less build/index.css",
}

The bootstrap package itself builds to dist, but I use build for the custom package since it will be in the project’s .gitignore by default. You can use whatever you like, but remember to ignore it unless you check in builds.

Link to the custom bootstrap module

I want to be able to make changes to the custom Bootstrap package and see the results in my react app quickly, so I used npm link to add the custom Bootstrap module to my existing create-react-app project:

$ npm link # in the custom-bootstrap directory
$ cd ../ # back to project root folder
$ npm link custom-bootstrap

Import custom Bootstrap in the react app

I was already using Bootstrap in my project so I updated the reference:

# previously
# import 'bootstrap/dist/css/bootstrap.css'
# using custom package:
import 'custom-bootstrap'

I don’t use the Bootstrap JavaScript files so I left them out of my custom build. If you do use them you can either add them to your custom package or depend on Bootstrap directly in your react app and import them.

Now use npm run build to recompile Bootstrap and your create-react-app based app should pick up changes automatically.

If you get an error that it cannot find custom-bootstrap, you probably forgot to use build/index.css as the value for the entry in your package.json file during init. No biggie, just change it now.

A global friendly naming scheme

To avoid naming conflicts when using this approach for multiple projects (npm link names are treated globally to a user), choose a naming scheme such as {project}-bootstrap and update the name field in package.json accordingly. This also works with npm scoping, using a name such as @project/bootstrap. Whichever scheme you choose, you’ll need to update the name when linking:

npm link "@project/bootstrap"

and when importing:

import '@project/bootstrap'

Add a watcher

Because the custom Bootstrap package is a package like any other you can customize it as desired. For example, you can add a watcher to automatically rebuild Bootstrap when index.less is saved. This is turn will be automatically picked up by a running react app so the client will be updated as well. I used npm-watch for my project:

  "scripts": {
"build": "lessc index.less build/index.css",
"start": "npm-watch"
},
"watch": {
"build": "index.less"
},

Simultaneously run dev server and custom module build watcher

At this point the development server and custom-bootstrap build watcher run in separate terminals. For most projects your custom bootstrap build will change infrequently, and so this is probably good enough. If your custom module is for something which will change more often, you might try npm-run-all. You can run both builds simultaneously from a single command:

$ npm install -g npm-run-all
$ npm-run-all --parallel start start:custom-bootstrap # long form
$ run-p start start:* # short form

To make this work add a start:custom-bootstrap script in package.json:

"scripts": {
"start": "react-scripts start",
"start:custom-bootstrap": "(cd custom-bootstrap && npm start)",
...
}

Using the form start:* it’s easy to add additional custom modules.

Updated 2017–03–19: Added section for scoping the custom package name.

Updated 2017–11–11: Moved instructions on updating entry point earlier in the article, to the section on init (both npm init and yarn init ask for a value) so @import 'custom-bootstrap' can be used right off the bat.