Header image: Ejection seat test at China Lake with F-4B cockpit (1967), public domain (US).
Update 2019-05-15: Starting from version 3.0, Create React App will respect the baseurl
settings that you have in your tsconfig.json
. If you already use CRA 3.0 and above, this guide is no longer necessary.
Create React App (CRA) 2.0 allows for drop-in support for TypeScript. This means we can finally use TypeScript from the core CRA package without having to rely on community-maintained forks like create-react-app-typescript. Thanks to Babel 7’s TypeScript support, the TypeScript integration to CRA 2.0 is seamless. Although this means that slight differences from create-react-app-typescript
apply.
One TypeScript feature that lacks from create-react-app
and TypeScript is the ability to use absolute imports. This allows importing from an absolute path based on the project’s root source directory. It means you can import modules without having to resort to relative paths. Nothing wrong with them, but when your folders are deeply nested, they can sometimes go ugly. (../../../../../../../
, anyone?)
Unfortunately, with CRA 2.0 making use of Babel for transpilation, this feature is rather limited. And they made it clear by forcing some defaults on this behaviour. For instance, CRA 2.0 automatically removes the baseUrl
and paths
options from the tsconfig.json
any time we run its scripts. This means for some people wanting to make use of this feature, ejecting is the only option.
Not anymore. Thanks to some clever little hacks and using CRA rewiring tools, we can now set up CRA 2.0 to respect TypeScript’s absolute imports without ejecting.
Before we start
Please note that rewiring CRA scripts is a slightly-advanced technique. Proceed with caution!
By rewiring your scripts, your’re breaking the “guarantees” that React provides. You now accept that you “own” your configs, which means that the CRA team cannot give support for your rewired config.
“Stuff can break” — Dan Abramov
But fortunately, with tools like craco
, we can easily revert back to the default CRA config should things go wrong. It’s still a better option over ejecting CRA entirely.
Set up craco
craco is a wonderful tool which allows us to extend CRA 2.0 configs without ejecting. It’s the spiritual successor for react-app-rewired, which has ceased development.
# yarn
$ yarn add @craco/craco
# npm
$ npm install @craco/craco
After craco
is installed, create an empty craco.config.js
file. We’ll touch this later.
// craco.config.js
module.exports = {
// ...
}
And finally, change the npm scripts in the package.json
to the following:
"scripts": {
- "start": "react-scripts start",
+ "start": "craco start",
- "build": "react-scripts build",
+ "build": "craco build"
- "test": "react-scripts test",
+ "test": "craco test"
}
This will run the CRA scripts through craco
which will inject the rewires set in craco.config.js
to the scripts.
Extend paths
option from a separate file
CRA 2.0 will automatically override the paths
config on the base tsconfig.json
every time we run npm start
. So we’ll have to work around it by defining it from a separate file.
Name this file whatever you’d like. This file exists solely because CRA 2.1 overwrites our base tsconfig every time we run npm start
. Always define paths in this file!
tsconfig.paths.json
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"//": "Define absolute imports path mappings.",
"//": "Don't forget to add these to `resolve.alias` on `craco.config.js`.",
"*": ["src/*"]
}
}
}
This allows us to set our absolute imports relative to the src
folder, e.g. components/Container
. If you prefer to use wildcard characters like ~
, you can also use that:
{
"//": "Allows you to import modules with the `~/components/Container` format.",
"compilerOptions": {
"baseUrl": "./",
"paths": {
"~/*": ["src/*"]
}
}
}
Then, we can extend the config above in our main tsconfig.json
file.
tsconfig.json
{
"//": "Extend the config we just created",
"extends": "./tsconfig.paths.json",
"compilerOptions": {
"//": "the rest of the tsconfig"
}
}
Rewire webpack aliases using craco
Now it’s time to tell the transpiler to alias a root module directory to its correct path. There are two ways to do this, one through Babel using babel-plugin-module-resolver
, and the other by setting webpack aliases. We’ll do the latter.
Open your craco.config.js
file and add the webpack config rewire.
// craco.config.js
const { paths } = require('@craco/craco')
module.exports = {
webpack: {
alias: {
// Add the aliases for all the top-level folders in the `src/` folder.
assets: `${paths.appSrc}/assets/`,
components: `${paths.appSrc}/components/`,
interfaces: `${paths.appSrc}/interfaces/`,
modules: `${paths.appSrc}/modules/`,
utils: `${paths.appSrc}/utils/`,
// Another example for using a wildcard character
'~': `${paths.appSrc}/`
}
}
}
Start the development server again, and now your absolute imports should work!
Configure Jest’s moduleNameMapper
If you use Jest, you should also let it know how to handle absolute imports. We can, of course, do this through craco
.
module.exports = {
webpack: {
alias: {
// Add the aliases for all the top-level folders in the `src/` folder.
assets: `${paths.appSrc}/assets/`,
components: `${paths.appSrc}/components/`,
interfaces: `${paths.appSrc}/interfaces/`,
modules: `${paths.appSrc}/modules/`,
utils: `${paths.appSrc}/utils/`,
// Another example for using a wildcard character
'~': `${paths.appSrc}/`
}
},
jest: {
configure: {
moduleNameMapper: {
// Jest module mapper which will detect our absolute imports.
'^assets(.*)$': '<rootDir>/src/assets$1',
'^components(.*)$': '<rootDir>/src/components$1',
'^interfaces(.*)$': '<rootDir>/src/interfaces$1',
'^modules(.*)$': '<rootDir>/src/modules$1',
'^utils(.*)$': '<rootDir>/src/utils$1',
// Another example for using a wildcard character
'^~(.*)$': '<rootDir>/src$1'
}
}
}
}
And that’s it!
Your CRA 2.0 setup now respects TypeScript’s absolute imports. Pretty simple, right?
Enjoy the power of TypeScript in your React app! If you want to learn more about how TypeScript can help you in React, check out the React TypeScript Cheatsheet. It also includes cheatsheets for common React patterns in TypeScript.
Top comments (3)
Nice! We are still using the react-scripts-typescript fork which made this easy.
I do wish TS would start to support moduleDirs so we can do better shared path resolution!
Fortunately, support for absolute imports using the
@
wildcard might be a possibility in CRA 3.0!While there's nothing wrong with
react-scripts-ts
, we did switch toreact-scripts
2.0 at work for the improved Babel integration, which allows us to use Babel plugins for libraries likestyled-components
. Fortunately our upgrade path was fairly frictionless.Also, I forgot to mention it in the post but someone did fork
react-scripts-ts
in an effort to keep it up to date! github.com/jpavon/react-scripts-tsCool, only works on my mac, someone using this on Linux? I keep getting the "Cannot find module:" error