Introduce TypeScript to react(js) project with ts-migratešŸ˜Ž

0xkoji - Sep 8 '20 - - Dev Community

TypeScript has been getting popular since it makes js codebase legit.

Dropbox did a huge migration!!! This is worth reading.

The Great CoffeeScript to Typescript Migration of 2017
https://dropbox.tech/frontend/the-great-coffeescript-to-typescript-migration-of-2017

Last month Airbnb released a really interesting package.

Learn about how we used codemods to accelerate migration from JavaScript to TypeScript at Airbnb. https://t.co/s1WihkfD1F

ā€” Airbnb Engineering (@AirbnbEng) August 18, 2020

In this article, I would like to introduce a small case that I tried.

Create a new branch

git checkout -b feature/ts-migrate

# you can use `switch` if your git version is later 2.23
git switch feature/ts-migrate
Enter fullscreen mode Exit fullscreen mode

Install ts-migrate

$ yarn add -D ts-migrate
# or
$ npm install --save-Dev ts-migrate
Enter fullscreen mode Exit fullscreen mode

Run ts-migrate

In this step, we need to avoid using yarn since there is an issue, so we need to use npx.
The issue has been fixed(https://github.com/airbnb/ts-migrate/commit/50f1a6cbc08c241c7f6ce822ca999cb197ae1e92)
So, npx and yarn, both of them are working.

In this article, the target folder is src

$ npx ts-migrate-full src
Welcome to TS Migrate! :D

This script will migrate a frontend folder to a compiling (or almost compiling) TS project.

It is recommended that you take the following steps before continuing...

1. Make sure you have a clean git slate.
   Run `git status` to make sure you have no local changes that may get lost.
   Check in or stash your changes, then re-run this script.

2. Check out a new branch for the migration.
   For example, `git checkout -b koji--ts-migrate` if you're migrating several folders or
   `git checkout -b koji--ts-migrate-src` if you're just migrating src.

3. Make sure you're on the latest, clean master.
   `git fetch origin master && git reset --hard origin/master`

4. Make sure you have the latest npm modules installed.
   `npm install` or `yarn install`

If you need help or have feedback, please file an issue on Github!

Continue? (y/N)
Enter fullscreen mode Exit fullscreen mode

before

Alt Text

Set a custom path for the typescript compiler. (It's an optional step. Skip if you don't need it. Default path is ./node_modules/.bin/tsc.):
Your default tsc path is ./node_modules/.bin/tsc.

[Step 1 of 4] Initializing ts-config for the "src"...

Config file created at /Users/koji/Documents/github/React-Hooks-StarterFiles/react-hooks-videoplayer/src/tsconfig.json
[test/ts 2249a33] [ts-migrate][src] Init tsconfig.json file
 2 files changed, 69 insertions(+)
 create mode 100644 src/.eslintrc
 create mode 100644 src/tsconfig.json
/Users/koji/Documents/github/React-Hooks-StarterFiles/react-hooks-videoplayer

[Step 2 of 4] Renaming files from JS/JSX to TS/TSX and updating project.json\...

Renaming 21 JS/JSX files in /Users/koji/Documents/github/React-Hooks-StarterFiles/react-hooks-videoplayer/src...
Done.
[test/ts d5675b0] [ts-migrate][src] Rename files from JS/JSX to TS/TSX
 21 files changed, 0 insertions(+), 0 deletions(-)
 rename src/components/{Darkmode.js => Darkmode.tsx} (100%)
 rename src/components/{PlaylistHeader.js => PlaylistHeader.tsx} (100%)
 rename src/components/{PlaylistItem.js => PlaylistItem.tsx} (100%)
 rename src/components/{Video.js => Video.tsx} (100%)
 rename src/components/containers/{App.js => App.tsx} (100%)
 rename src/components/containers/{Playlist.js => Playlist.tsx} (100%)
 rename src/components/containers/{PlaylistItems.js => PlaylistItems.tsx} (100%)
 rename src/components/containers/{WbnPlayer.js => WbnPlayer.tsx} (100%)
 rename src/components/hoc/{withLink.js => withLink.tsx} (100%)
 rename src/components/styles/{GlobalStyle.js => GlobalStyle.ts} (100%)
 rename src/components/styles/{StyledDarkmode.js => StyledDarkmode.ts} (100%)
 rename src/components/styles/{StyledJourney.js => StyledJourney.ts} (100%)
 rename src/components/styles/{StyledPlaylist.js => StyledPlaylist.ts} (100%)
 rename src/components/styles/{StyledPlaylistHeader.js => StyledPlaylistHeader.ts} (100%)
 rename src/components/styles/{StyledPlaylistItem.js => StyledPlaylistItem.ts} (100%)
 rename src/components/styles/{StyledPlaylistitems.js => StyledPlaylistitems.ts} (100%)
 rename src/components/styles/{StyledVideo.js => StyledVideo.ts} (100%)
 rename src/components/styles/{StyledVideoWrapper.js => StyledVideoWrapper.ts} (100%)
 rename src/components/styles/{StyledWbnPlayer.js => StyledWbnPlayer.ts} (100%)
 rename src/{index.js => index.tsx} (100%)
 rename src/{serviceWorker.js => serviceWorker.ts} (100%)
/Users/koji/Documents/github/React-Hooks-StarterFiles/react-hooks-videoplayer

[Step 3 of 4] Fixing TypeScript errors...

forkTSServer
Logs in /var/folders/gj/x6v5vwdx1v7741fdfcxwmr100000gn/T/ts-migrate-log-iiAQGO
TypeScript version: 3.9.7
Initialized tsserver project in 207.623ms.
Start...
[strip-ts-ignore] Plugin 1 of 12. Start...
[strip-ts-ignore] Finished in 53.092ms.
[hoist-class-statics] Plugin 2 of 12. Start...
[hoist-class-statics] Finished in 25.675ms.
[react-props] Plugin 3 of 12. Start...
[react-props] Finished in 15.874ms.
[react-class-state] Plugin 4 of 12. Start...
[react-class-state] Finished in 1.483ms.
[react-class-lifecycle-methods] Plugin 5 of 12. Start...
[react-class-lifecycle-methods] Finished in 10.154ms.
[react-default-props] Plugin 6 of 12. Start...
[react-default-props] Finished in 0.768ms.
[react-shape] Plugin 7 of 12. Start...
[react-shape] Finished in 0.989ms.
[declare-missing-class-properties] Plugin 8 of 12. Start...
[declare-missing-class-properties] Finished in 1517.982ms.
[explicit-any] Plugin 9 of 12. Start...
[explicit-any] Finished in 363.076ms.
[eslint-fix] Plugin 10 of 12. Start...
[eslint-fix] Finished in 1148.936ms.
[ts-ignore] Plugin 11 of 12. Start...
[ts-ignore] Finished in 370.630ms.
[eslint-fix] Plugin 12 of 12. Start...
[eslint-fix] Finished in 193.324ms.
Finished in 3703.120ms, for 12 plugin(s).
Writing 21 updated file(s)...
Wrote 21 updated file(s) in 3.488ms.
rm: src/.eslintrc.*: No such file or directory
Enter fullscreen mode Exit fullscreen mode

If everything goes well, ts-migrate will convert .js files to .ts and .tsx. In addition, create tsconfig.json and .eslintrc.js

after

Alt Text

Note:tsconfig.json and .eslintrc.js are generated under src.

Install typescript and @types files

If ts-migrate did this step automatically, that would be amazing, but actually, right now it doesn't. We need to install typescript and @types files.
If there is no @type file, you will need to create global.d.ts for packages that don't support typescript.

$ yarn add -D typescript
Enter fullscreen mode Exit fullscreen mode

Install ts-loaderwebpack.config.js

The react app I used in this article uses webpack to bundle .js files. The project needs ts-loader to support typescript.

Now, it's time to open src folder with an editor. I use Visual Studio Code.

Remove ts-something

ts-migrate has put ts-error and ts-migrate(7016) FIXME to avoid errors.

Alt Text

Most cases will be solved by installing @types files for npm packages so what we need to do is to remove comments.

Modify webpack.config.js

This project is using webpack to bundle js files, but now files are .ts, or .tsx so need to modify webpack configurations a little bit.
The most important thing is to switch from .js to .tsx or .ts

For example, entry, extensions and using ts-loader etc.

/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const autoprefixer = require('autoprefixer');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: './src/index.tsx',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    chunkFilename: '[id].js',
    publicPath: '',
  },
  devtool: 'inline-source-map',
  devServer: {
    contentBase: './',
    open: true,
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
      },
      // {
      //     test: /\.(gif|png|svg|jpg)$/,
      //     loader: "url-loader"
      // },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {
              modules: {
                localIdentName: '[name]__[local]___[hash:base64:5]',
              },
              sourceMap: true,
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: () => [autoprefixer({})],
            },
          },
        ],
      },
      {
        test: /\.(svg|png|jpe?g|gif)$/,
        loader: 'url-loader?limit=10000&name=img/[name].[ext]',
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.json'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: __dirname + '/public/index.html',
      filename: 'index.html',
      inject: 'body',
    }),
  ],
};
Enter fullscreen mode Exit fullscreen mode

Update npm script

I just added .ts and .tsx as a target file like below.

  "scripts": {
    ...
    "lint": "eslint 'src/**/*.{ts,tsx}'",
    "lint:prettier": "prettier --check './src/**/*.{js,ts,tsx}'"
  }
Enter fullscreen mode Exit fullscreen mode

Small modification

If you see some .tsx or .ts code, you notice that ts-migrate just use any for all variable which isn't good but it totally makes sense as a migration tool's behavior since ts-migrate doesn't know about your project.

In this case, I know the project itself since I wrote this code recently which means I still remember the variables' types. So I updated any to a proper data-type.

Conclusion

In my opinion, ts-migrate is very useful. It will make a codebase legit and reduce the migration cost a little bit.

I think if you don't think that it's not time to do the migration, still trying ts-migrate can be useful.

For example, I tried ts-migrate for an open-source library which is written in javascript and saw a bug that a function defines 2 parameters, but it is given 3 parameters(of course, the extra one isn't used). You may find out this kind of issue on your codebase then you can back to js to fix the issues you detected lol.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .