Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / React

Create React App: Customize Webpack Config Without Ejecting

4.00/5 (1 vote)
24 Apr 2017CPOL3 min read 9.7K  
Create React App: Customize Webpack Config Without Ejecting

Create React App comes with a great config out of the box, and it has the "eject" feature for when you want to take the config into your own hands.

But what if you just want to add a couple little tweaks to the Webpack config without having to keep the entire config up to date by yourself?

We'll go over how to do that here.

Warning!

First, a word of warning: if you are unfamiliar with how Webpack works, or are not comfortable maintaining the (small amount of) hacky code that we'll produce here, I recommend skipping this one. This is rather advanced magic.

As Create React App (specifically its react-scripts package) changes, it is HIGHLY LIKELY that the code below will eventually need some repairs. If their Webpack config changes structure, or it's exported differently, or any number of other things happens, this code will break, and it will be up to you to figure out how it works and fix it. So, again: if you're not comfortable with that idea, don't do this.

Hacking Create React App

Ok, with that scary disclaimer out of the way, let's figure out how to hack Create React App. If you want to skip straight to the Example Project, that's fine too.

The underpinnings of Create React App are housed in the "react-scripts" package, which you'll see listed under "devDependencies" in package.json.

We're going to use rewire to monkey-patch react-scripts and allow us to customize the Webpack config before it executes.

This file here is the biggest piece of that puzzle. I suggest making a directory called "scripts" inside your CRA project and putting this code in scripts/customized-config.js. You can name it whatever you like, though (we'll need the name again later).

scripts/customized-config.js

JavaScript
/*
  This module runs the scripts from react-scripts (Create React App)
  and gives an opportunity to override the Webpack config by creating
  a "config-overrides.dev.js" or "config-overrides.prod.js" file in the
  root of the project.

  The config-override file should export a single function that takes a
  config and returns the modified config, like this:

  module.exports = function(webpackConfig) {
    return webpackConfig;
  };
*/
var rewire = require('rewire');
var proxyquire = require('proxyquire');

switch(process.argv[2]) {
  // The "start" script is run during development mode
  case 'start':
    rewireModule('react-scripts/scripts/start.js', loadCustomizer('../config-overrides.dev'));
    break;
  // The "build" script is run to produce a production bundle
  case 'build':
    rewireModule('react-scripts/scripts/build.js', loadCustomizer('../config-overrides.prod'));
    break;
  // The "test" script runs all the tests with Jest
  case 'test':
    // Load customizations from the config-overrides.testing file.
    // That file should export a single function that takes a config and returns a config
    let customizer = loadCustomizer('../config-overrides.testing');
    proxyquire('react-scripts/scripts/test.js', {
      // When test.js asks for '../utils/createJestConfig' it will get this instead:
      '../utils/createJestConfig': (...args) => {
        // Use the existing createJestConfig function to create a config, then pass
        // it through the customizer
        var createJestConfig = require('react-scripts/utils/createJestConfig');
        return customizer(createJestConfig(...args));
      }
    });
    break;
  default:
    console.log('customized-config only supports "start", "build", and "test" options.');
    process.exit(-1);
}

// Attempt to load the given module and return null if it fails.
function loadCustomizer(module) {
  try {
    return require(module);
  } catch(e) {
    if(e.code !== "MODULE_NOT_FOUND") {
      throw e;
    }
  }

  // If the module doesn't exist, return a
  // noop that simply returns the config it's given.
  return config => config;
}

function rewireModule(modulePath, customizer) {
  // Load the module with `rewire`, which allows modifying the
  // script's internal variables.
  let defaults = rewire(modulePath);

  // Reach into the module, grab its global 'config' variable,
  // pass it through the customizer function, and then set it back.
  // 'config' is Create React App's built-in Webpack config.
  let config = defaults.__get__('config');
  config = customizer(Object.assign({}, config));
  defaults.__set__('config', config);
}

To make this work, you'll need to install a few extra packages:

npm install --save-dev rewire proxyquire

You can pretty much read the comments to figure out how it works. The interesting part is the rewireModule function at the bottom, which uses the rewire library to modify variables in another file.

Once you've got that in place, you can write the config-overrides files for dev, prod, and test. This part is really up to you - whatever changes you need to make to CRA's Webpack config, go right ahead.

These files should go directly in the root of your CRA folder, and all 3 are optional. If you want to relocate them, just change the path in the "loadCustomizer" calls above. Just don't put them in "src".

Here's an example of some dev overrides:

config-overrides.dev.js

JavaScript
<code class="language-js">const path = require('path');

module.exports = function(config) {
  // Use your own ESLint file
  config.eslint.useEslintrc = true;

  // Make CRA's "catchall" loader ignore .scss files
  config.module.loaders[0].exclude.push(/\.scss$/);

  // Add the SASS loader
  config.module.loaders.push({
    test: /\.scss$/,
    loaders: ["style", "css", "sass"]
  });

  return config;
}

To make this work, you'll need to install the SASS loader:

npm install --save sass-loader node-sass

Finally, to trigger all this new code, you'll need to change package.json to call this new customized-config script instead of the default react-scripts. To do that, replace the "start", "build", and "test" lines with these:

package.json

JavaScript
"scripts": {
  "start": "node scripts/customized-config start",
  "build": "node scripts/customized-config build",
  "test": "node scripts/customized-config test --env=jsdom",
}

Example Project

A CRA-generated project with these mods applied is up on Github here.

Create React App: Customize Webpack Config Without Ejecting was originally published by Dave Ceddia at Dave Ceddia on April 23, 2017.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)