Include different repositories in a create-react-app project
Tags: cra, javascript, webpack, configuration
Published: 2019-07-09 22:33:25

*Cover image: Bunbeg Beach, Co. Donegal, Ireland*

When I started writing React applications all the codebase was under a single repository. No code sharing, no context separation.

As soon as I gain interested in exploring new solutions, I wanted to build a small dashboard at home as a playground to test new libraries, React hooks or integration with other frameworks like Vue.

Creating the Dashboard backbone was a straightforward operation: a few components for the scaffold, a basic authentication module, a Routes component with few switch cases and an `apps` folder to contain the different projects.

Coming from a Python/Django background, I wanted to organize my code in different repositories as I was doing with all my previous projects. Unfortunately this operation has not been as straightforward as expected.

In Python there are a couple of tools which I am familiar with and that helped me to manage this requirement: `virtualenvwrapper` is one of these.
One of its functionalities (`add2virtualenv`) is the ability to link different repositories together under the same environment and still being able to modify them without any re-installation or deployment - another option would be `pip install -e` from the repository folder.

Unfortunately, it's not the same with npm/yarn and `create-react-app`; they both allows to `link` but each repository must resolve it's own dependencies and have them installed.

## Project structure

The currenct project structure is based on the standard `create-react-app` example.

```
package.json
src/
    apps/
        ...
    libs/
        ...
    scaffold/
        ...
    App.js
    index.js
```

My idea was to share the code inside `libs` folder to all the applications and to keep each application in it's own repository.

The first thing I tried was `yarn link`, but it didn't worked well. This approach assumes that the code inside the application is already packaged with it's own dependencies resolved. 

Something was missing!

## Avoid ejecting

The first thing I did was to `eject` to explore the configuration opportunities.
This is actually a good way to test out configurations if you can revert (which is pretty easy if you are under a version controlled folder).

Ejecting (and keeping each config file) however is not my preferred solution because when you go manual for a long period and starts to customize a lot of things you can't easily go back, and you need to maintain the dependencies one by one.

On the opposite side, `create-react-app` does not allow you to modify the configuration files in order to keep the project as generic and standard as possible.

Few solutions has been suggested on the web: switch to razzle.js, use next.js, rethink the project structure with lerna (the monorepo approach - one owner only) or fork `create-react-app`.

In order to keep the project simple I didn't want to introduce next.js or razzle.js. I hope to reuse my dashboard code for other projects and using a framework might not be the best solution.

## Fork
Of all solutions I opted for forking `create-react-app` repository, following [this guide](https://medium.com/@denis.zhbankov/maintaining-a-fork-of-create-react-app-as-an-alternative-to-ejecting-c555e8eb2b63).

On `react-scripts` package, I added the following lines to `config/webpack.config.js`.

- saving the content to a variable instead of returing the configuration directly:

```diff
// remove the return statement and save the content
-  return {
+  let config = {
     mode: isEnvProduction ? 'production' : isEnvDevelopment &&  'development',
```

- checking if a file called `customWebpack.config.js` exists in the project root folder and, if it has a `modify` function, override the config with the function's result:

```diff
     // our own hints via the FileSizeReporter
     performance: false,
   };
+  console.log('Checking for custom webpack config');
+  if (fs.existsSync('./customWebpack.config.js')) {
+    console.log('  -- custom config found!');
+    const customWebpackConfig = require(path.resolve(
+      __dirname,
+      '../../../../customWebpack.config'
+    ));
+    if (customWebpackConfig.modify) {
+      config = customWebpackConfig.modify(config, { webpackEnv });
+    }
+  }
+
+  return config;
};
```

This similar approach is used by `razzle` (in a much fancier way).

On my project then I had to do three things:

- Adding a `customWebpack.config.js` file:

```javascript
module.exports = {
    modify: (config, { webpackEnv }) => {

        // List of external repositories that have to be added
        // to the testers to being correctly processed       
        let externalRepositories = [];
        if (process.env.REACT_APP_EXTERNAL_REPOSITORIES) {
            externalRepositories = process.env.REACT_APP_EXTERNAL_REPOSITORIES.split(',');
        }

        // Set a list of repositories required for this project
        const projectRepositories = [ 
            'my-test-repo'           
        ];

        // Validate that all repositories have been set before starting
        projectRepositories.forEach(repo => {
        if (externalRepositories.filter(eRepo => eRepo.endsWith(repo)).length !== 1)
            throw new Error(`==> Repository ${repo} must be included in ` +
                `.env.local REACT_APP_EXTERNAL_REPOSITORIES variable`);
        });

        config.module.rules[2].oneOf.forEach((test, index) => {
        if (test.include) {
            config.module.rules[2].oneOf[index].include = [
            ...(Array.isArray(test.include) ? test.include : [test.include]),
            ...externalRepositories,
            ];
        }
        });
    }
    return config;
};
```

- add the repositories to `REACT_APP_EXTERNAL_REPOSITORIES` in .env.local file:

```
REACT_APP_EXTERNAL_REPOSITORIES=~/repositories/my-test-repo
```

- and finally created a link 

```
ln -s ~/repositories/my-test-repo dashboard-ui/src/apps/
```

## Final considerations

This approach is not a standard approach in JS/React applications/modules development, but allows me to have the advantages I have when I develop python applications:
- every module is in it's own repository: different repositories might have different visibility, permissions, follow different merging strategies and have it's own wiki (or even different team);
- changes are picked up immediately by CRA without extra compile steps;
- the application is bundled from one single point (however, this is also a disadvantage in terms of CI/CD, but `node_modules` can be cached to speed up build operations).

There are also some disadvantages, but for my pipeline they are not a problem:
- as mentioned, one change in a single repository requires the full application to be bundled again. A mixed approach can be adopted by adding the external repositories only in development leveraging `webpackEnv` variable, building single modules with their own pipeline;
- setup for a new repository is not straightforward: we need to create a link and add the repository to an env variable (this might be automated as well), and we need to do this in order to build.

Can you see a simpler way to achieve this result? You think this is a terrible approach? Please share your opinion!