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
Need to remove
FlowStatusWebpackPlugin
, unless you want to support both typescript and flow files.