Incremental migration of a “create-react-app” JavaScript project to TypeScript (without eject 🎆😉
)
We are developing an Excel Addin using ReactJs+UI Fabric React. This application is working already in production but there are new features in our product roadmap. We started using Office UI Fabric React 6 which didn’t support TypeScript but this year Fabric React 7 was released and TypeScript is now supported! 🚀😂🤩
Now for us, these are good and bad news. We wanted to integrate the new version because of improvements and stability, but our code base is a little bit large. We evaluate to migrate everything to TypeScript but we do not have the time to do it. I did a little bit of research and think what can we do so we can at least prepare for a future migration but at the same time still working on new (pending) features 😵🥶. We have another applications in JavaScript that we want to migrate, so this could be a great moment to do something experimental.
The Excel Addin application we used as a start point was create with the classic create-react-app
, which we migrated react-scripts
to version 3.2.0
following the official docs.
Migrating to latest version of
create-react-app
it’s not painful, if you haven’t made major changes. You almost need to upgradepackage.json
, re-runyarn start
orreact-scripts start
and follow the suggestions.
Now let’s start migrating our code base to TypeScript (incrementally of course 😉), but before we can start save your changes first please😇.
Project structure and packages
Our current project structure is similar to this (it is the default for create-react-app
).
src/
//ALL JS+JSX FILES
.gitignore
package.json
The first step is to create a folder with name assets
inside src
folder, here we are going to move any css
or png
or any static file that is in src
root. If you do not have files, just create this folder for future changes.
src/
assets
//ALL JS+JSX FILES
.gitignore
package.json
Fix your references in code if you move any file to assets
folder. After this changes, copy the src
folder to a folder called ts
in the same path. Inside ts
create a file with name tsconfig.json
. Also create two more files at the same level that ts
folder, this files should be named tsconfig.build.json
and tsconfig.lint.json
.
src/
assets
//ALL JS+JSX FILES
ts/
assets
//ALL JS+JSX FILES
tsconfig.json
tsconfig.build.json
tsconfig.lint.json
.gitignore
package.json
Now we need to install the following packages in order to integrate TypeScript:
yarn add typescript tslib
yarn add --dev @types/node @types/react @types/jest @types/react-dom @types/react-redux
TypeScript configuration
Now let’s edit tsconfig.lint.json
by adding the following content to this file. Please check that in outDir
we are writting src
because there we are going to ouput our transpiled files, so we will not eject
our project. Check also baseUrl
, this configuration will allow us to use non relative imports in TypeScript. Check include
configuration, we are specifying our sources path. Finally check exclude
configuration in which we are adding src
in order to centralize our code in ts
folder. This tsconfig.lint.json
file is used for linting and checking .js
and .ts
files.
This file will not emit any code, it is used to analyze code only.
tsconfig.lint.json
{
"compilerOptions": {
"outDir": "src",
"module": "esnext",
"target": "ES6",
"lib": ["es6", "dom"],
"sourceMap": false,
"inlineSourceMap": true,
"allowJs": true,
"jsx": "preserve",
"moduleResolution": "node",
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"baseUrl": "ts",
"checkJs": false,
"resolveJsonModule": true,
"esModuleInterop": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"importHelpers": true
},
"include": ["ts"],
"exclude": ["node_modules", "build", "scripts", "acceptance-tests", "webpack", "jest", "src"],
"types": []
}
Depending of your configuration, you may want to specify the jsx
value to preserve, react or react-native
. According to docs:
TypeScript ships with three JSX modes:
preserve
,react
, andreact-native
. These modes only affect the emit stage - type checking is unaffected. Thepreserve
mode will keep the JSX as part of the output to be further consumed by another transform step (e.g. Babel). Additionally the output will have a.jsx
file extension. Thereact
mode will emitReact.createElement
, does not need to go through a JSX transformation before use, and the output will have a.js
file extension. Thereact-native
mode is the equivalent ofpreserve
in that it keeps all JSX, but the output will instead have a.js
file extension.
Let’s edit tsconfig.build.json
by adding the following content to this file. Please check that we are extending from tsconfig.lint.json
in order to reuse the configuration that we already write. This file is used to generate code, therefore here we specify include
again, but only for .ts
files, the other files will be copied without almost any change, except sourcemaps
.
tsconfig.build.json
{
"extends": "./tsconfig.lint.json",
"include": ["ts/**/*.ts", "ts/**/*.tsx"]
}
Finally let’s edit ts/tsconfig.json
. This file, as we said, is inside ts
folder.
This file is also used to trick VSCode, so when we open our project it will be fully analyzed. If you use another IDE, please verify your requirements.
ts/tsconfig.json
{
"extends": "../tsconfig.lint.json"
}
Cleaning and running
Delete all files and folders in src
except the assets
folder. Then modify your .gitignore
file (if necessary) to ignore the transpiled files.
src/**/*.js
src/**/*.json
Now to transpile our code, run in console the following command:
yarn tsc --project tsconfig.build.json --watch
And then start your project as you normally do, for example (using yarn
or react-scripts
depending of your configuration):
yarn start
Open your site and verify that everything is working fine and that’s all.
Testing
You can add a component just to verify that tsx files works, for example: let’s create for example a file in ts/components
called tsx-test.tsx
.
src/
assets
//ALL JS+JSX FILES
ts/
components/
tsx-test.tsx //just for testing purposes 😅
assets
//ALL JS+JSX FILES
tsconfig.json
tsconfig.build.json
tsconfig.lint.json
.gitignore
package.json
Write this content:
ts/components/tsx-test.tsx
import React from "react";class TsxProps {}function TsxTest(props: TsxProps) {
return <div>Hello from TS and JS project</div>;
}export default TsxTest;
And add to any page or view that you have. It will be transpiled and accepted correctly by react-scripts
:
VSCode
You can build your project in VSCode as a standard task. You can add or modify your build task in VSCode (tasks.json) to include our new profile:
.vscode/tasks.json
{
"version": "2.0.0",
"command": "gulp",
"isBackground": true,
"echoCommand": false,
"type": "shell",
"windows": {},
"tasks": [
{
"type": "typescript",
"tsconfig": "tsconfig.build.json",
"option": "watch",
"problemMatcher": [
"$tsc-watch"
],
"group": "build"
}
]
}
Also we need to exclude the old files from VSCode explorer in settings.json:
.vscode/settings.json
{
// ... more stuff ...
"files.exclude": {
// ... more files ...
"src/{[^a],?[^s],??[^s],???[^e],????[^t],?????[^s]}*": true
},
// ... more stuff ...
}
Final notes
✋ Maybe you will have to make some minor changes depending of your imports (relative or non relative).
👍 Non relative paths still working! For example we can do: import TsxTest from “components/tsx-test”;
👌 It’s probable that react-scripts start throws some warnings because of code style, but remember that now it is generated so do not worry (Instead it is an error not a warning)
That’s all folks! Feedback is welcome. Let’s keep learning.