Shared Typescript in CRA

Sharing is caring (for your project structure)

Keeping your project maintainable with growing complexity and more and more code is no easy task and may overwhelm you when being first confronted with. One core principal during development: DRY, “don’t repeat yourself”. A good and thorough strategy on the upcoming implementations helps greatly to work out the patterns of common functions and data types and subsequently allows you to avoid repetitions. This is especially true when making full use of Typescript’s Generics.

But what if your project itself consists of other projects? A common example would be the development of a frontend and backend in the same project. As you’ll see in a moment, Typescript allows us to directly use code of other Typescript-projects anywhere on the filesystem as long as they can be referenced.

How you can do it

Yet if you’re using a build-tool such as Webpack, Rollup etc. (which at least for frontend, you’re probably using right now), things might get a little tricker when setting up. Note that the following example represents just one of a handful of approaches, but this one works for us.

First, here's the overall project structure, where we have three folders frontend, backend, shared:

|--- /frontend
|--- /backend
|--- /shared

To prepare your CRA-app for consuming shared code and types, first add the following to its tsconfig.json. This tells tsc to treat everything in this directory + in shared one level up the filesystem as source.

  "compilerOptions": {
    "rootDirs": [".", "../shared"]

This will let tsc (TypeScript Compiler) detect the code in the providing project. Due to react-scripts configuration, building the app doesn’t yet work - and b/c react-scripts can’t be changed, we have to replace it with something that works. We could either eject, but then lose all of the great minifications and optimizations as well. For a graceful replacement, react-app-rewired is a great choice: it acts as a drop-in-replacement for react-scripts, yet allows us to modify the webpack-config. Nice!

Add react-app-rewired as a dev-dependency (npm i -D react-app-rewired) and update the package.json as following:

  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",

Now what’s only left is to re-enable the building by providing our adaptation in a file named config-overrides.js, according to the react-app-rewired documentation. As you can see, we change two things:

const { removeModuleScopePlugin, override, babelInclude } = require("customize-cra");
const path = require("path");

module.exports = override(removeModuleScopePlugin(), babelInclude([path.resolve("src"), path.resolve("../shared")]));

  • Allow imports outside of src, which isn’t possible in vanilla CRA
  • Include the  imported code in babel’s transpilation

Almost there

For the project that acts as the sharing source, setup a tsconfig.json as following:

  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "composite": true,
    "outDir": "lib"
  "include": ["src"],
  "exclude": ["node_modules", "lib"],
  "references": []

All the functions, type definitions, etc. get into a directory src. If you want to, this config transpiles your TypeScript to JS into a folder lib.

For other consumers of the shared code, such as a backend-project, just update the tsconfig as you’ve done for the frontend prior and you should be good to go. If you’re using a build-tool there, too, just check how to include the files in your minification-step, as we’ve done in frontend here as well.

And yes, that’s about it! Due to including the code in frontend’s babel-transpilation, we don’t need to rebuild the shared project after changes - the code gets used as-is.

I hope to have helped you in improving your code’s maintainability!

- Tom

expressFlow is now Lean-Forge