Background: How to quickly convert an old React project to TS style when you are working on some old front-end projects, sometimes with js and TS at the same time.

ts compiler

The link is the official typescript introduction, and I’ll briefly cover some of the basic concepts of TS Compiler.

  • Parser produces the AST from source code
  • The Type Checker can analyze to generate inferred types
  • Emitter generator
  • The pre-processor analyzes source code dependencies and generates a series of sourcefiles

The target

Basically we need to convert a JSX-style React Component to a TSX-style Component

import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'

@connect(
  (state, props) = > {
    const { app, global, info, list, curItem } = state

    return {
      curApp: list[props.id],
      app,
      curItem,
      total: info.total,
      name: global.name,
    }
  },
  dispatch =>
    bindActionCreators(
      {
        save,
        update,
        remove,
      },
      dispatch
    )
)
export default class Test extends Component {
  static propTypes = {
    obj: PropTypes.object,
    isBool: PropTypes.bool,
    str: PropTypes.string,
    arr: PropTypes.array,
    oneOfType: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    node: PropTypes.node,
    oneOf: PropTypes.oneOf(['a'.'b'.'c'.'d']),
    func: PropTypes.func,
    required: PropTypes.func.isRequired,
  }

  constructor(props) {
    super(props)
    this.state = {
      isShowModal: false.modalName: props.report_name || ' '.modalType: 'save'.confirmLoading: false.monitorModalVisible: false,}this.aaa = '111'
  }

  render() {
    return <div>hello tscer</div>}}Copy the code

What do we need to do

  • Convert static propTypes to Interface IProps
  • Generate interface IState from this.state
  • Generate generic types of Component
  • Remove PropTypes

Before we start we need an AST Viewer to help us quickly analyze the AST tree structure,hereLet’s copy the code above and look at the AST tree structure.

To analyze

The entrance

import { CompilerOptions, createPrinter, createProgram, EmitHint, transform } from 'typescript'

const program = createProgram([realPath], compileOptions)

  const sourceFiles = program
    .getSourceFiles()
    .filter(s= > s.fileName === realPath)

  const typeChecker = program.getTypeChecker()

  const result = transform(sourceFiles, [
    generateGenericPropAndState(typeChecker),
    removeImportPropTypes(typeChecker),
    removeStaticPropTypes(typeChecker),
  ])

  const printer = createPrinter()
  const printed = printer.printNode(
    EmitHint.SourceFile,
    result.transformed[0],
    sourceFiles[0])const res = prettier.format(printed, {
    semi: true,
    singleQuote: true,
    trailingComma: 'es5',
    bracketSpacing: true,
    parser: 'typescript',})Copy the code
  • createProgram

    • The first parameter is the array of addresses we need to compile the file
    • The second parameter is the tsconfig.json compiler options under the TS project. You can read the target file’s compileOptions, or select some of the default options.
    • Simply speaking, a program is the pre-processor, according to the parameter TS Compiler will generate a series of target file compiler depends on the library, generate a series of SourceFiles
  • program.getSourceFiles()

    • To get the compiled SourceFile object, we need to filter the target file we need, because the default TS Compiler will add a series of target files depend on some files, such as an ES DOM lib library, we only need the target file.
  • transform

    • Similar to Babel’s traverse analysis, the essence of this is to facilitate the AST, analyze each node, and then do the action we need to do
    • Generate IProps and IState generateGenericPropAndState do is
    • RemoveImportPropTypes Removes the import PropTypes
    • RemoveStaticPropTypes Removes static PropTypes
  • createPrinter

    • Printer is the last generator that generates our final code
    • Finally, formatting tools like Prettier, tsLint, and so on can generate the code you need for your project

removeImportPropTypes

import { isImportDeclaration, isStringLiteral, SourceFile, updateSourceFileNode } from 'typescript'

export const removeImportPropTypes = (typeChecker: TypeChecker) = > (
  context: TransformationContext
) => (sourceFile: SourceFile) = > {
  const statements = sourceFile.statements.filter(
    s= >! ( isImportDeclaration(s) && isStringLiteral(s.moduleSpecifier) && s.moduleSpecifier.text ==='prop-types'))return updateSourceFileNode(sourceFile, statements)
}

Copy the code
  • Here is more of the thought process, code details you can try it out for yourself

  • A higher order equation for transform

    • The first parameter, typeChecker, is passed by ourselves in the transform
    • The second argument, context, enables the context information to be saved throughout the compilation process
    • The third parameter, sourceFile, is the sourceFile we need to compile
  • The structure of sourceFile, this is the ast viewwe that I mentioned earlier

  • The middle SourceFile is the structure of the SourceFile, select the code can also see the corresponding AST structure of the code

  • Here we need to remove the import PropTypes from ‘prop-types’, which corresponds to a structure called ImportDeclaration

    • Let’s look at the rightmost Node Node in the figure
    • What we need is an import declaration called prop-types, which is obviously on the right inside moduleSpecifier -> Text
    • Here we have what we need. Find a node in sourceFile where the ImportDeclaration moduleSpecifier text is ‘prop-types’ and remove it.
  • Sourcefile. statements represent each code block

  • The filter removes prop-types according to the logic I described above

  • Finally we return updateSourceFileNode, after generating an update we need the new sourceFile to return

  • The subsequent transform function is similar to this thinking process. Because of the TS structure, VScode has good code hints and type annotations, some TS Compiler apis can be quickly adapted to according to the corresponding Node definition

removeStaticPropTypes

export const removeStaticPropTypes = (typeChecker: TypeChecker) = > (
  context: TransformationContext
) => (sourceFile: SourceFile) = > {
  const visitor = (node: Node) = > {
    if (isClassDeclaration(node) && isReactClassComponent(node, typeChecker)) {
      return updateClassDeclaration(
        node,
        node.decorators,
        node.modifiers,
        node.name,
        node.typeParameters,
        createNodeArray(node.heritageClauses),
        node.members.filter(m= > {
          if (
            isPropertyDeclaration(m) &&
            isStaticMember(m) &&
            isPropTypesMember(m)
          ) {
            // static and propTypes
            return false
          }

          if (
            isGetAccessorDeclaration(m) &&
            isStaticMember(m) &&
            isPropTypesMember(m)
          ) {
            // static and propTypes
            return false
          }

          return true}}))return node
  }

  return visitEachChild(sourceFile, visitor, context)
}
Copy the code

  • VisitEachChild traverses the Ast tree, giving a callback on each node
  • We need to get rid of static propTypes. The first is a ClassDeclaration, the second is a React Component, and the last class has a PropertyDeclaration. The name is propTypes
    • ClassDeclaration good judgment, isClassDeclaration
    • React Component, here we need to analyze the heritage of the ClassDeclaration, namely the inheritance condition. As shown in the figure, we need to obtain HeritageClause. Types [0].expression.gettext (), here we can use the regex to determine, / React.Com ponent | Component | PureComponent | React. PureComponent /, basic cases this is the React class Component
    • IsPropertyDeclaration can determine whether it isPropertyDeclaration, as shown in the figure. PropertyDeclaration. Modifiers [0] = = = StaticKeyword, here judge whether the modification is a static, PropertyDeclaration. Name = = = ‘propTypes’ to judge.

generateGenericPropAndState

  • The thought process is the same as above, so you can try it out for yourself. The source code
  • For the generated ast structure of the new code, you can enter the required code into the AST Viewer and observe the generated AST structure to build structures such as interfaces.

conclusion

What else can you do

  • For example, convert Redux Connect to Hoc and use returnType to get some types of Redux
  • Add an undefined attribute to the IProps
  • Add parameters for IProps, IState, and params to some of the declaration cycles
  • You can take some simple functions, use checker to generate some types, and add TODO to complex types for later additions.
  • According to some project directories, batch processing JS, JSX to convert to the required TS files
  • Do a rollback, if not ideal, the user can roll back to the original JS file
  • .

Ts compile can do some expected operations according to your needs, similar to Babel’s Parser, traverse, generator, etc. Here is just a general idea to provide you, after all, many small program frameworks are similar. happy coding!