I’m usingcreate-react-app lately, in an effort to simplify. Until now I’ve been using customnpm 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 customizedBootstrap 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 thecreate-react-app
maintainer does not plan to include support for less, but he did offera smart workaround using tools already at our disposal. In a gist:
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.
$ cd example-react-app
$ mkdir custom-bootstrap
$ cd custom-bootstrap
$ npm init # (or yarn init)
During init usebuild/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.
Bootstrap, of course, but also less, which will be used to compile custom css.
$ npm install --save bootstrap less # or yarn add bootstrap 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.
"scripts": {
"build": "lessc index.less build/index.css",
}
The bootstrap package itself builds todist
, but I usebuild
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.
I want to be able to make changes to the custom Bootstrap package and see the results in my react app quickly, so I usednpm 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
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 usenpm 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 usebuild/index.css
as the value for theentry
in yourpackage.json file during init. No biggie, just change it now.
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 thename
field inpackage.json accordingly. This also works withnpm 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'
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 usednpm-watch
for my project:
"scripts": {
"build": "lessc index.less build/index.css",
"start": "npm-watch"
},
"watch": {
"build": "index.less"
},
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 trynpm-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 astart:custom-bootstrap
script inpackage.json:
"scripts": {
"start": "react-scripts start",
"start:custom-bootstrap": "(cd custom-bootstrap && npm start)",
...
}
Using the formstart:*
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.