Use SCSS with create-react-app

By default, create-react-app comes without scss enabled. We are going to create a workflow to enable watching, compiling and exporting .scss files to import them as .css files into our components — all without having to eject.

What You Need

  • create-react-app installed.
  • node installed.
  • Basic knowledge of React.

Make It

Being able to integrate a streamlined workflow that converts .scss files to .css without our direct involvement will make us more productive and efficient in our React development.

Recipe Guide

Building CSS from SCSS

Let's start by creating a sample react application:

create-react-app sample-app

Then, we first need to install the command-line interface for Sass as follows:

yarn:

yarn add node-sass-chokidar

npm:

npm install --save node-sass-chokidar

After the package is installed, in our package.json, we are going to create a script to compile .scss to .css:

 "build-css": "node-sass-chokidar src/ -o src/"

The script build-css takes the .scss files present within the src folder and subfolders and compiles them to .css files. The .css file will be present in the same location as the original .scss file. Without having to think about any other paths, it becomes easy to import the .css file in the components that we are styling.

Watching SCSS and CSS

Next, we are going to create a script to run build-css to compile any existing files into .css but also to keep watching the src folder for changes — as in changes to the content of existing .scss files or the addition of new ones:

"watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive"

Our initial package.json would look like this:

{
  "name": "sample-app",
  "version": "0.1.0",
  "private": true,
  "devDependencies": {
    "react-scripts": "1.1.1"
  },
  "dependencies": {
    "node-sass-chokidar": "^0.0.3",
    "react": "^16.2.0",
    "react-dom": "^16.2.0"
  },
  "scripts": {
    "build-css": "node-sass-chokidar src/ -o src/",
    "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive",
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

Let's test these scripts. Within src, create test.scss with the following content:

body {
  background: red;
}

Run your React app and notice that there are no changes yet. At this point, we cannot import the styles of test.scss into any of our components yet since we need its .css version. Let's run the build-css command manually:

yarn:

yarn build-css

npm:

npm run build-css

Depending on your development environment, you may see the .css file nested within the .scss file or adjacent to it. I am using WebStorm and my .css file is nested. If you are using Visual Studio Code, for example, the file is adjacent.

We can now import test.css into the App component. Open App.js and add the following import:

import "./test.css";

Save App.js and now your React app should have a blinding red background. Let's change it to a softer color. Go back to test.scss and change its contents to:

body {
  background: lightcoral;
}

Save test.scss and look at the browser. Nothing happened. That's because we have not run build-css manually. Run the script once again and the background color should change. Running this script manually is very tedious and that's why we created that watch-css script. Let's run our watch script to see its time-savings benefits:

yarn:

yarn watch-css

npm:

npm run watch-css

Let's go back to test.scss and change the background color to lightblue. Save it. This time the browser reloaded and the new background color is being displayed.

Let's add a new file to test that our watch-css script is recognizing new .scss sources. Create another-test.scss within the src folder with the following content:

body {
  color: navy;
}

Save the file. You won't see any changes in the browser (because the file has not been imported anywhere) but you will see that another-test.css has been created. Let's import it into App.js and then we should see the text color in the browser change to navy.

We have achieved the task of automating the compilation of .scss into .css files that we can import into our components for styling; however, as of now, we would have to run watch-css manually every time we start the project. What we are going to do next is to run this script when we start the project.

Building and Watching SCSS from the Start

We are going to rename the current start script to start-react.

From

"start": "react-scripts start"

To

"start-react": "react-scripts start"

Do not forget to add commas at the end of every JSON property when there is more than one property — if you do, it would break your package.json file.

We are going to recreate start as a script that runs both start-react and watch-css in parallel. Out of the box, npm doesn't offer that functionality, but we can use a handy package named npm-run-all that exactly does that for us.

Install npm-run-all:

yarn:

yarn add npm-run-all

npm:

npm install npm-run-all

Once the package is installed, we create start as follows:

"start": "npm-run-all -p watch-css start-react"

The -p flag signals npm-run-all to run the commands that follow in parallel.

Stop your application if it's running and rerun it again:

yarn:

yarn start

npm:

npm run start

Once again, go to test.scss and change the background color to lightseagreen. Save the file. This time the browser updates and showcases the new background color.

One last important step that we need to take is to update our build script. When we build our project, we need to ensure that any .scss file is compiled. We are going to rename the existing build to build-react and then create new script logic for build — just as we did with start:

"build-react": "react-scripts build",
"build": "npm-run-all -s build-css build-react"

This time around, we want to build-css first and then build-react. Using npm-run-all we can do that by specifying the -s flag which signals it to run the following commands in sequence.

Conclusion

Your final package.json should look like this:

{
  "name": "sample-app",
  "version": "0.1.0",
  "private": true,
  "devDependencies": {
    "react-scripts": "1.1.1"
  },
  "dependencies": {
    "node-sass-chokidar": "^0.0.3",
    "npm-run-all": "^4.1.2",
    "react": "^16.2.0",
    "react-dom": "^16.2.0"
  },
  "scripts": {
    "build-css": "node-sass-chokidar src/ -o src/",
    "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive",
    "start-react": "react-scripts start",
    "start": "npm-run-all -p watch-css start-react",
    "build-react": "react-scripts build",
    "build": "npm-run-all -s build-css build-react",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

You can save this package.json somewhere else and simply copy and paste its contents into the body of the package.json of new projects you start.

We were able to bring scss power into the context of create-react-app without resorting to eject by using npm scripts and the npm-run-all package. As always, I hope that you enjoyed this blog post. If you want to share improvements or comments on this process of handling scss, feel free to reach out to me on Twitter, @getDanArias. Thanks for reading!

Enjoy!