Helpful Migration Guides:
This include, but are not limited to:
flow-typed
folder- flowconfig
flow-typed
in eslintignore
Why? TypeScript now supports transpiling JavaScript.
yarn remove eslint-loader
yarn remove babel-loader
yarn add --dev awesome-typescript-loader source-map-loader typings-for-css-modules-loader
If you have a project that needs to support both JS and TS, update babel
rm .babelrc
touch babel.config.js
// babel.config.js
module.exports = function babelConfig (api) {
api.cache(true);
return {
presets: [
"@smartling/babel-preset-smartling",
"@babel/preset-typescript"
],
plugins: [
"@babel/plugin-transform-modules-commonjs"
],
env: {
test: {
plugins: ["@babel/plugin-transform-modules-commonjs"]
}
}
};
};
Assuming we went with babel-loader
:
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const webpack = require("webpack");
const isDevelopmentMode = process.env.NODE_ENV === "development";
module.exports = {
bail: !isDevelopmentMode,
cache: true,
devtool: isDevelopmentMode ? "eval-source-map" : false,
mode: isDevelopmentMode ? "development" : "production",
module: {
rules: [
{
test: /\.(t|j)sx?$/,
exclude: /node_modules/,
loader: "eslint-loader",
enforce: "pre"
},
{
test: /\.(t|j)sx?$/,
exclude: /node_modules/,
loader: "babel-loader",
query: {
cacheDirectory: true
}
},
{
test: /\.(woff2?|ttf|eot|svg)(\?[\s\S]+)?$/,
loader: "url-loader",
options: {
name: "[name]-[hash].[ext]"
}
}
]
},
plugins: [
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV)
}),
new MiniCssExtractPlugin({ filename: "editor.css", allChunks: true })
],
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts", ".scss", ".json", ".css"],
modules: [
path.resolve(__dirname, "../src"),
path.resolve(__dirname, "../node_modules")
]
}
};
After updating the webpack config, build the project (or run webpack) to generate the .d.ts files for your css / sass / pcss. This will ensure that these modules can be imported into your TypeScript project.
For more on TypeScript + WebPack + Sass
- https://stackoverflow.com/a/55993985/4399522
- https://medium.com/@chris_72272/migrating-to-typescript-write-a-declaration-file-for-a-third-party-npm-module-b1f75808ed2
- https://medium.com/@sapegin/css-modules-with-typescript-and-webpack-6b221ebe5f10 <== This one should work. Need to add typings-for-css-module
- https://www.npmjs.com/package/typings-for-scss-modules-loader
- https://medium.com/@kvendrik/generating-typings-for-your-css-modules-in-webpack-2beb3739b342
- https://stackoverflow.com/questions/45411909/loading-sass-modules-with-typescript-in-webpack-2
yarn add --dev typescript @types/react @types/react-dom @types/jest @types/enzyme @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-import-resolver-typescript
Add the TS version of all your library code
yarn add @types/deep-freeze --dev
yarn add @types/chai --dev
yarn add @types/classnames
etc etc
scripts: {
"type-check": "tsc"
}
Guides
- https://storybook.js.org/docs/configurations/typescript-config/
- https://dev.to/swyx/quick-guide-to-setup-your-react--typescript-storybook-design-system-1c51 with sample project
Add all the dependencies
yarn add -D @storybook/react @storybook/addon-info @storybook/addon-jest @storybook/addon-knobs @storybook/addon-options @storybook/addons @storybook/react storybook-addon-jsx @types/react babel-core typescript awesome-typescript-loader react-docgen-typescript-webpack-plugin jest @types/jest ts-jest
Create a .storybook
directory at the root of your project
mkdir .storybook
touch .storybook/config.js .storybook/addons.js .storybook/webpack.config.js
{
"compilerOptions": {
// Target latest version of ECMAScript.
"target": "esnext",
// Search under node_modules for non-relative imports.
"moduleResolution": "node",
// Process & infer types from .js files.
"allowJs": true,
// Don't emit; allow Babel to transform files.
"noEmit": true,
// Enable strictest settings like strictNullChecks & noImplicitAny.
"strict": false,
// Disallow features that require cross-file information for emit.
"isolatedModules": true,
// Import non-ES modules as default imports.
"esModuleInterop": true,
"jsx": "react",
"module": "esNext"
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [
"src/**/*.js"
]
}
//eslintrc.js
module.exports = {
extends: [
"plugin:@typescript-eslint/recommended",
"eslint:recommended",
"@smartling/eslint-config-smartling"
],
parser: "@typescript-eslint/parser",
plugins: ["import", "@typescript-eslint"],
parserOptions: {
ecmaFeatures: {
jsx: true
},
sourceType: "module",
useJSXTextNode: true,
project: "./tsconfig.json"
},
rules: {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/member-delimiter-style": [2],
"import/extensions": [1, "never", { ts: "never", "json": "always" }],
"react/jsx-indent": [0],
"react/jsx-indent-props": [0],
"indent": [0]
},
overrides: [
{
files: ["**/*.ts", "**/*.tsx"],
rules: {
"semi": 1,
"no-unused-vars": ["off"],
"quote-props": ["error", "as-needed"]
}
}
],
settings: {
"import/resolver": {
node: {
extensions: [".js", ".jsx", ".ts", ".tsx"]
},
"eslint-import-resolver-typescript": true
},
"import/parsers": {
"@typescript-eslint/parser": [".ts", ".tsx"]
},
react: {
version: "detect"
}
},
env: {
jest: true,
browser: true
}
};
I suggest excluding plugin:@typescript-eslint/recommended
for a big project at first to facilitate an incremental migration.
Open VSCode setting (See more about setting up TSLint at http://artsy.github.io/blog/2019/01/29/from-tslint-to-eslint/ and https://dev.to/dorshinar/linting-your-reacttypescript-project-with-eslint-and-prettier-8hb)
{
"eslint.autoFixOnSave": true,
"javascript.validate.enable": false,
"editor.minimap.enabled": false,
"git.enableSmartCommit": true,
"window.zoomLevel": 1,
"workbench.activityBar.visible": true,
"javascript.updateImportsOnFileMove.enabled": "always",
"typescript.implementationsCodeLens.enabled": true,
"editor.formatOnSave": true,
"eslint.validate": [
{
"language": "javascript",
"autoFix": true
},
{
"language": "javascriptreact",
"autoFix": true
},
{
"language": "typescript",
"autoFix": true
},
{
"language": "typescriptreact",
"autoFix": true
}
]
}
$ sudo touch migrate.sh
$ vim migrate.sh
#!/bin/bash
cd $1
for f in `find . -type f -name '*.js'`;
do
mv -- "$f" "${f%.js}.ts"
done
Then provide the directory that you want to migrate as first argument of and execute the script.
$ chmod +x migrate.sh $ ./migrate.sh ~/tinext-editor/src
// Flow
import type { Type1, Type2 } from ./dir/to/path
import { type Type3 } from ./dir/to/path
// Typescript
import { Type1, Type2 } from ./dir/to/path
Check out
When in doubt, try things out in TypeScript Playground or repl
This is helpful if you are migrating from Flow to TypeScript: https://github.com/niieani/typescript-vs-flowtype
In flow, we use type
.
In TypeScript, use interface
. Interface types offer more capabilities they are generally preferred to type aliases. For instance:
- An interface can be named in an extends or implements clause, but a type alias for an object type literal cannot.
- An interface can have multiple merged declarations, but a type alias for an object type literal cannot.
// Flow
type User = {
firstName: string,
lastName: string,
email: string
}
const user = {
firstName: "Jane",
lastname: "Doe",
email: "[email protected]",
paidUser: true
};
const user2 = {
firstName: "Jane",
lastname: "Doe"
};
const userAsUser: User = ((user: User): User);
const user2AsUser: User = ((user2: User): User); // Fails
The casting for user2AsUser
fails with the following error from flow:
Cannot cast
user2
toUser
because propertyUser
// TypeScript
interface User {
firstName: string;
lastName: string;
email: string;
}
const user: User = {
firstName: "Jane",
lastName: "Jane Doe",
} as any as User;
In flow, Plus sign in front of property Means it’s read-only https://stackoverflow.com/questions/46338710/flow-type-what-does-the-symbol-mean-in-front-a-property
In Typescript, use the “readonly” keyword https://mariusschulz.com/blog/read-only-properties-in-typescript
In Flow
type MyList = {
filter: Array<*> => Array<*>,
head: Array<*> => *
}
In TypeScript
class List<T> {
filter: T[] => T[];
head: T[] => T;
}
For more on Generics in TypeScript: https://www.typescriptlang.org/docs/handbook/generics.html
interface Entry {
name: string;
id: string;
}
interface EntryWithData<T> extends Entry {
data?: T[];
lastUpdated?: Date;
}
const stuff: EntryWithData<number> = {
data: [1,2,3] // TS error: name and id are required for EntryWithData
}
In flow, we use Array<ObjectType>
In TypeScript, we use ObjectType[]
There's no analog of enum in Flow. Enum in TypeScript allow us to make a collection of constants as types. Some examples
Combining two enums
enum Mammal {
DOG = "DOG",
HORSE = "HORSE",
HUMAN = "HUMAN"
}
enum Insect {
ANT = "ANT",
BEE = "BEE",
FLY = "FLY"
}
const Animal = {
...Mammal,
...Insect
}
type AnimalT = Mammal & Insect
The naming convention for enums
Use a singular name for most Enum types, but use a plural name for Enum types that are bit fields.
Use union type
const enum BasicEvents {
Start = "Start",
Finish = "Finish"
}
const enum AdvEvents {
Pause = "Pause",
Resume = "Resume"
}
type Events = BasicEvents | AdvEvents;
let e: Events = AdvEvents.Pause;
const animals: AnimalT[] = [“DOG”, “ANT”, “HUMAN”, “BEE”]
const mammals: Mammal[] = animals.filter(animal => animal in Mammal)
console.log(mammals) //> [“DOG”, “HUMAN"]
Note the difference between Animal and AnimalT!
enum Var {
X = "x",
Y = "y",
}
type Dict = { [var in Var]: string };
This is a lot simpler than Flow. In Flow, we had to do something like this:
const Vars = {
X: "x",
Y: "y"
}
type VarType = $Keys<typeof Vars>
type Dict = { [var in VarType]: string }
interface Animal {
LION = "LION",
PIG = "PIG",
COW = "COW",
ANT = "ANT"
}
type DomesticatedMammals = {
[animal in Exclude<Animal, Animal.ANT>]: boolean
}
enum One {
A = "A",
B = "B",
C = "C"
}
enum Two {
D = "D",
E = "E",
F = "F"
}
const map = new Map<string, One | Two>([
["a", One.A],
["d", Two.D]
])
$Shape<SomeObjectType>
in Flow is analogous to creating Generics or other types to extend in typescript.
TypeScript sometimes does not recognize Tuple Types. Solution: explicit casting or typing when declaring new var
Example
type Interval = [number, number]
const getMaxAndMin = (interval: Interval) => ({
min: interval[0],
max: interval[1]
});
const interval = [0, 3];
getMaxAndMin(interval);
TypeScript complains:
Argument of type 'number[]' is not assignable to parameter of type '[number, number]'. Type 'number[]' is missing the following properties from type '[number, number]'
Solution:
const interval: Interval = [0, 3];
getMaxAndMin(interval);
Or
getMaxAndMin([0,3]);
class MyButton extends React.Component {
constructor() {
this.handleClickBound = handleClick.bind(this);
}
handleClick() {
console.log("do something");
}
render() {
return (
<button onClick={handleClickBound}>Click Me</button>
)
}
}
const button = mount(<MyButton />);
const buttonInstance = button.instance();
buttonInstance.handleClick()
Property 'handleClick' does not exist on type 'Component<{}, {}, any>'
Solution:
const button = mount<MyButton>(<MyButton />);
- Fix all lint errors
- Fix
any
types
- Re-run unit tests to make sure they all pass
- Integration testing - if the project is a library, make sure you can build it and integrate it into a project that's not TypeScript.
- Update your CI/CD pipeline to include type checking as part of the build process. For example
// Jenkinsfile
stage('Type Check') {
sh 'yarn tsc'
}
TypeScript Learning Resources
- Tutorial for setting up loader for css module: https://medium.com/@sapegin/css-modules-with-typescript-and-webpack-6b221ebe5f10
- TypeScript Handbook: https://www.typescriptlang.org/docs/handbook/interfaces.html
- Do's and Don'ts https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html
- TypeScript Deep Dive: https://basarat.gitbooks.io/typescript/docs/types/type-assertion.html
- Typescript VS Code Integration http://artsy.github.io/blog/2019/01/29/from-tslint-to-eslint/
- TypeScript function: https://mariusschulz.com/blog/typing-destructured-object-parameters-in-typescript
- TypeScript and React best practice: https://medium.freecodecamp.org/effective-use-of-typescript-with-react-3a1389b6072a
- Advanced Types https://www.typescriptlang.org/docs/handbook/advanced-types.html
- TypeScript Eslint Config:
- TypeScript Docs: https://basarat.gitbooks.io/typescript/docs/const.html
- Narrow Interfaces TypeScript https://medium.com/@OlegVaraksin/narrow-interfaces-in-typescript-5dadbce7b463
- So Many Useful TypeScript Tips! https://codeburst.io/five-tips-i-wish-i-knew-when-i-started-with-typescript-c9e8609029db
- Type Assertion https://www.tutorialsteacher.com/typescript/type-assertion
Actually no need to remove babel, as it not only remove types from code, it also converts ES syntax to older one for browser compatibility. Changing babel to some other plugin you have risk of regression.