Cast turn reference

A growing number of projects are embracing typescript instead of javascript, such as ant-Design, as we know it. In the face of the growing popularity of typescript, our company has embraced typescript this year. Why use typescript? This article does not go into depth, interested in this aspect of friends can go to see this article:

TypeScript architecture research report

This article provides a comprehensive overview of TypeScript and a comparison with Javascript. After reading this article, you should have a better understanding of TypeScript and a better choice between TypeScript and Javascript.

Starting the migration

Before starting the migration, I want to digression. This article is about documenting the problems I encountered during the migration and how I solved them, not about teaching typescript. Before reading this article, it’s important to have a basic understanding of typescript, or else you’ll struggle to read it.

Environment adjustment

Because Typescript is a superset of Javascript, many of its syntax is unrecognized by browsers, so it doesn’t run directly in browsers. It needs to be compiled into Javascript to run in browsers. This is the same reason ES6 needs to be compiled with Babel to support more older browsers.

tsconfig.json

First we need to install typescript, just as we need to install Babel-core before we can use Babel.

yarn add typescript 
Copy the code

Some people choose to install typescript globally, but I personally recommend installing typescript in the project directory, since the typescript version of each project is not exactly the same, and installing typescript globally can cause problems depending on the version. If you need to use TSC, you can use NPX. Next we generate tsconfig.json by executing the following command, which is identical to babelrc.

npx tsc --init
Copy the code

After executing, your project will have a tsconfig.json file in its root directory, but it will have a lot of comments in it, so let’s not worry about that.

webpack

Ts-loader is installed to process TS and TSX files, similar to babel-loader.

yarn add ts-loader -D
Copy the code

The corresponding webpack needs to add the ts loader rule:

Module.exports = {// omit some code... module: { rules: [ {test:/\.tsx? $/, loader:'ts-loader'} // omit some code... ] } / /... Omit some code}Copy the code

So when we used javascript, some people didn’t use.jsx files, the whole project was going to use.js files, and webapck didn’t even have the rules of.jsx. However, using all.ts files in a typescript project doesn’t work and will cause errors, so you still have to use.tsx files when using JSX, so I added the.tsx rules here.

Delete the Babel

There are a lot of people on the web who want to keep Babel for the simple reason that they don’t want to use JavaScript in the future, but I personally don’t think it’s necessary to keep Babel. Because most of the time, we only use javascript in the project when we use third-party packages, and these third-party packages are mostly compiled into ES5 code and don’t need Babel to handle them. It’s even less possible to use javascript for business logic, since it makes no sense to use typescript. In summary, I personally feel that it is necessary to remove Babel related things and reduce the complexity of the project. But there is one exception:.

When you use Babel plug-ins that have features that typescript doesn’t provide, you can keep Babel and combine it with typescript.

File name adjustment

All the SRC now. Js at the end of the file to change the file name, use the JSX grammar. To TSX composite file, unused to. Ts file, the workload is bigger, will be a headache. In addition, there will be a lot of red marks in the document after modification, so don’t rush to change it, we will change it in a unified manner.

To solve the error

The Webpack entry file cannot be found

Module.exports = {// omit some code... entry: { app:'./src/main.tsx'}, // omit some code... }Copy the code

Indicates that JSX syntax cannot be used

{
   "compilerOptions": {"jsx": "react"}}Copy the code

The JSX configuration option has three options: “Preserve “,”react-native”, and “react”. JSX is preserved in the generated code in Preserve and React-Native mode for subsequent conversion operations (e.g., Babel). In addition, the preserve output file will carry the.jsx extension, while react-native is the.js extension. The react mode generates react. CreateElement, which does not need to be converted before use. The output file has a.js extension.

model The input The output Output file extension
preserve <div /> <div /> .jsx
react <div /> React.createElement(“div”) .js
react-native <div /> <div /> .js

The alias configured in Webpack cannot be resolved

Module.exports = {// omit some code... resolve: {alias: {The '@':path.join(__dirname,'.. /src'} // omit some code... }, // omit some code... }Copy the code

{
    "compilerOptions": {"baseUrl": "."."paths": {
          "@ / *": ["./src/*"]}}}Copy the code

See the typescript documentation for details, but note that baseUrl and Paths must work together.

www.tslang.cn/docs/handbo…

The module cannot be found because the extension name cannot be added automatically

Module.exports = {// omit some code... Resolve: {// omit some code... extensions: ['.js'.'.jsx'.'.json'}, // omit some code... }Copy the code

But all the.js and.jsx files in our project have been changed to.ts and.tsx files, so the configuration needs to be adjusted.

{// omit some code... Resolve: {// omit some code... extensions: ['.ts'.'.tsx'.'.js'.'.jsx'.'.json'}, // omit some code... }Copy the code

Could not find a declaration file for module ‘**’

If you can’t find the module declaration file, you can install the module declaration file as follows:

yarn add @types/**
Copy the code

For example 🌰, if you say Could not find a declaration file for module ‘react’, you should run the following command:

yarn add @types/react
Copy the code

This is limited to third party packages, if the project’s own module indicates that the declaration file is missing, you will need to write the corresponding declaration file. For example, if you mount an object on the global object Window, you need to declare it if you want to use it. Otherwise, an error will be reported. How to do this depends on the typescript documentation, which I won’t expand here.

www.tslang.cn/docs/handbo…

Cannot find type definition file for ‘**’

{
    "compilerOptions": {"typeRoots": ["node_modules". ."./src/types"]}}Copy the code

In fact the default value of typeRoots is “[“@types”]”, all visible “@types” packages will be loaded during editing, such as “./node_modules/@types/ “, “.. / node_modules / @ types “and”.. /.. /node_modules/@types/ and so on are loaded. So for this type of problem, your configuration should change to:

{
    "compilerOptions": {"typeRoots": ["@types". ."./src/types"]}}Copy the code

In a real project, @types basically exists in node_modules in the root directory, so here you can change it to something like this:

{
    "compilerOptions": {"typeRoots": ["node_modules/@types". ."./src/types"]}}Copy the code

Decorators not supported

{
    "compilerOptions": {"experimentalDecorators":true}}Copy the code

Module ‘**’ has no default export

{
    "compilerOptions": {"allowSyntheticDefaultImports":true}}Copy the code

Of course you can also use the configuration items “esModuleInterop”, it is set to true, according to the “allowSyntheticDefaultImports” default values, as follows:

module === "system" or --esModuleInterop
Copy the code

The “esModuleInterop” configuration item has two main functions:

  • Provide __importStar and __importDefault helpers to be compatible with the Babel ecosystem
  • Open allowSyntheticDefaultImports

For “esModuleInterop” and choose “allowSyntheticDefaultImports” on, if you need a typescript in combination with Babel, there is no doubt that choose “esModuleInterop”, otherwise, Choose “allowSyntheticDefaultImports” personal habits, prefer to do what. Of course “esModuleInterop” is the safest option, so if in doubt, just use “esModuleInterop”.

Global objects such as Document and window cannot be recognized

{
    "compilerOptions": {"lib": ["dom". ."esNext"]}}Copy the code

Red marking problem in file

Now, we need to think about this in two different ways, the first is the.ts file, and the second is the.tsx file. (Ps: The points mentioned below can not completely solve the problem of red marking, but can solve most of the problem of red marking) :

First:.ts files

This kind of file is relatively few in your project, it is easier to handle, according to the actual situation to add a type limit, there is no special need to talk about.

The second type:.tsx files

React components are stateless and stateful. React components are stateless and stateful.

Stateless component

For a stateless component, first limit it to a FunctionComponent and second limit its props type. An 🌰 :

import React, { FunctionComponent, ReactElement } from 'react';
import {LoadingComponentProps} from 'react-loadable';
import './style.scss'; interface LoadingProps extends LoadingComponentProps{ loading:boolean, children? :ReactElement } const Loading:FunctionComponent<LoadingProps> = ({loading=true,children})=>{
  return( loading? <div className="comp-loading">
      <div className="item-1"></div>
      <div className="item-2"></div>
      <div className="item-3"></div>
      <div className="item-4"></div>
      <div className="item-5"></div>
    </div>:children
  )  
}
export default Loading;
Copy the code

You can use the type alias “SFC” or “FC” if you think the FunctionComponent name is long.

Stateful component

For stateful components, there are three main points to note:

  1. Both props and state need type restrictions
  2. State Limits the operation of this.state=** with readonly
  3. Type restrictions on event objects
import React,{MouseEvent} from "react";
interface TeachersProps{
  user:User
}
interface TeachersState{
  pageNo:number,
  pageSize:number,
  total:number,
  teacherList:{
    id: number,
    name: string,
    age: number,
    sex: number,
    tel: string,
    email: string
  }[]
}
export default class Teachers extends React.PureComponent<TeachersProps,TeachersState> {
    readonlystate = { pageNo:1, pageSize:20, total:0, userList:[] } handleClick=(e:MouseEvent<HTMLDivElement>)=>{ console.log(e.target); } / /... Omit some coderender() {return<div onClick={this.handleClick}> Click me </div>}}Copy the code

In a real project, the component’s state might have many values, which would be cumbersome to write as we did above, so consider the following shorthand:

import React,{MouseEvent} from "react";
interface TeachersProps{
  user:User
}
const initialState = {
  pageNo:1,
  pageSize:20,
  total:0,
  teacherList:[]
}
type TeachersState = Readonly<typeof initialState>
export default class Teachers extends React.PureComponent<TeachersProps,TeachersState> {
    readonlystate = initialState handleClick=(e:MouseEvent<HTMLDivElement>)=>{ console.log(e.target); } / /... Omit some coderender() {return<div onClick={this.handleClick}> Click me </div>}}Copy the code

This writing method will simplify a lot of code, but the type restriction effect is obviously not as good as the first one, so this method is only for reference, according to the actual situation to choose.

Ant Design lost the style file

When we start the project, some students’ pages may have style loss, as follows:

babel-plugin-import
babel-plugin-import
ts-import-plugin

yarn add ts-import-plugin -D
Copy the code

This plugin needs to be used in conjunction with ts-Loader, so the webPack configuration needs to be adjusted as follows:

const tsImportPluginFactory = require('ts-import-plugin'Module. Exports = {// omit some code... module:{ rules:[{test: /\.tsx? $/, loader:"ts-loader",
            options: {
                transpileOnly: true,// (optional) getCustomPluginFactory: () => ({before: [tsImportPluginFactory({libraryDirectory:'es',
                        libraryName: 'antd',
                        style: true})]})}}]} }Copy the code

Note that transpileOnly: True is an optional configuration that I recommend only for large projects, not small projects. Because typescript’s semantic checker checks all files at compile time, compilation times can be long when projects are large. The easiest way to solve this problem is to use transpileOnly: true to turn off typescript semantic checking, but at the cost of type checking and exporting declaration files, this configuration is not recommended except in large projects to improve compilation efficiency.

After the configuration is complete, your browser console may report an error like the following:

  • Module set to “commonJS”
  • Target set “ES5” but not module (module defaults to “commonJS” when target is not “ES6”)

The solution is to set the Module to “esNext” to solve the problem.

{
    "compilerOptions": {"module":"esNext"}}Copy the code

Some guys may say that setting it as “ES6” or “ES2015” is also ok. As for why I choose “esNext” instead of “ES6” or “ES2015”, the main reason is that it cannot be dynamically imported after setting it as “ES6” or “ES2015”. Because the project uses the react-loadable package, if set to “ES6” or “ES2015”, the following error will be reported:

Speaking of the Module argument, a bit more about the moduleResolution parameter, which determines how typescript handles modules. The moduleResolution parameter can be ignored when the Module is set to “esNext”, but the moduleResolution parameter should be set when the project is set to “ES6”. Take a look at the moduleResolution default rule:

module === "AMD" or "System" or "ES6" ? "Classic" : "Node"
Copy the code

When our Module is set to “ES6”, moduleResolution defaults to “Classic” and we need “Node”. The main reason why we choose “Node” is that Node’s module resolution rules are more consistent with our requirements and are faster to parse. For details, see the Typescript documentation.

www.tslang.cn/docs/handbo…

Again, to be on the safe side, I recommend forcing moduleResolution to “Node.”

conclusion

The above are the problems I encountered in the process of migration, which may not cover the problems you encountered in the process of migration. If there are any errors I did not mention above, please let me know in the comment section, AND I will try my best to improve this article. Finally, this article is just a summary of my experience migrating to typescript. It doesn’t cover all configuration items for tsconfig.json. Finally, attach the address of the project I migrated to typescript:

Project address: github.com/ruichengpin…