博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
写一个基于 typescript compiler 的转换工具
阅读量:5743 次
发布时间:2019-06-18

本文共 6401 字,大约阅读时间需要 21 分钟。

背景:当你接手一些老的前端项目的时候,有时候这个项目同时又js和ts,如何快速将一个老的基于React的项目快速转换为ts风格。

链接是typescript官方的介绍,这里我简单说一说一些ts compiler的基本概念。

  • Parser 根据源代码生产ast
  • Type Checker 可以分析生成一些推断类型
  • Emitter 生成器
  • Pre-processor 分析源代码依赖,生成一系列SourceFile

目标

基本上我们需要将一个jsx风格的React Component 转换为一个tsx风格的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 
hello tscer
}}复制代码

我们需要做什么

  • 将 static propTypes 转换为 interface IProps
  • 根据 this.state 生成 interface IState
  • 生成 Component 的 generic types
  • 去掉 PropTypes

在开始之前我们需要个 ast viewer 帮我们快速分析ast树结构, ,让我们把上述代码复制进去, 看看ast树结构。

开始分析

入口

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',  })复制代码
  • createProgram

    • 第一个参数是我们需要编译文件的地址数组
    • 第二个参数是ts项目下面的tsconfig.json的一些编译选项,这里可以读取目标文件的compileOptions,或者自选一些默认的选项生成。
    • 简单来说一个program就是编译的入口(pre-processor),根据的参数ts compiler会生成一系列目标文件编译所依赖的库,生成一系列SourceFiles
  • program.getSourceFiles()

    • 获取编译后的SourceFile对象,这里需要filter一下我们需要的目标文件,因为默认ts compiler会添加一系列目标文件所依赖的一些文件,例如一个es dom lib库等, 我们需要的只是目标文件。
  • transform

    • 类似于babel分析里面的 traverse,本质就是便利ast,分析每个node, 然后做一个我们需要的操作
    • generateGenericPropAndState 做的就是生成IProps 和 IState
    • removeImportPropTypes 删除掉 import PropTypes
    • removeStaticPropTypes 删除掉 static PropTypes
  • createPrinter

    • Printer 就是最后的generator, 生成最终我们的代码
    • 最后可以利用 prettier, tslint等格式化工具生成你项目中需要的代码

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)}复制代码
  • 这里更多的会将思考过程,代码细节大家可以自己去试试就知道了

  • 一个transform的高阶方程

    • 第一个参数 typeChecker,使我们在transform中自己传递的
    • 第二个参数 context, 使整个编译过程中保存的上下文信息
    • 第三个参数 sourceFile,就是我们需要编译的源文件了
  • sourceFile的结构, 这里就用到我之前说的

  • 中间的SourceFile即是sourceFile的结构了,选择代码也可以看到代码对应的ast结构

  • 这里我们需要把 import PropTypes from 'prop-types' 删除掉,明细这里对应的是一个叫做 ImportDeclaration 的结构

    • 我们看看图中最右侧Node节点
    • 我们需要的是一个叫做 prop-types的import 声明,很明显在右侧它在 moduleSpecifier -> text 里面
    • 到这里我们就得到我们需要的了 找到一个sourceFile里面 ImportDeclaration的moduleSpecifier的text是'prop-types'的节点,去除掉即可。
  • sourceFile.statements 代表的是每一个代码块

  • filter就按照我以上说的逻辑 去除掉 prop-types

  • 最后返回 updateSourceFileNode, 生成了一个更新后我们需要新的sourceFile返回

  • 之后的transform功能类似于此的思考过程,由于ts结构vscode有很好的代码提示,以及类型注释,一些ts compiler的api大家根据对应Node的定义应该可以很快的适应

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)}复制代码

  • visitEachChild 遍历ast tree,在每一个node给一个回调
  • 我们需要去除掉 static propTypes, 前提如图, 首先是一个ClassDeclaration,其次是一个 React Component, 最后class里面有一个 PropertyDeclaration, static修饰,名字是propTypes
    • ClassDeclaration很好判断, isClassDeclaration
    • react Component, 这里我们需要分析 ClassDeclaration 的 HeritageClause,也就是继承条件。如图展示,我们需要获得 HeritageClause.types[0].expression.getText(), 这里可以利用正则去判断一下,/React.Component|Component|PureComponent|React.PureComponent/, 基本情况下这是react class component
    • isPropertyDeclaration可以判断是否是 PropertyDeclaration,如图,PropertyDeclaration.modifiers[0] === StaticKeyword, 这里判断其修饰是否是一个 static, PropertyDeclaration.name === 'propTypes' 去判断。

generateGenericPropAndState

  • 思考过程还是如上面所述,大家可以先自己尝试一下。
  • 关于生成的新的代码的ast结构, 大家可以在 输入需要的代码,在观察一下生成的ast结构,从而去构建例如 interface 等类型结构。

总结

还可以做什么

  • 例如将 redux connect转换为hoc,利用 returnType 获取redux的一些类型
  • 可以根据 class 里面 this.props 去分析, 添加一个没有定义的属性添加到 IProps 中间
  • 给一些声明周期添加一些IProps, IState, params的参数
  • 可以将一些 简单function, 利用checker生成一些类型,并在复杂类型添加TODO,给后期方便添加。
  • 可以根据一些project的目录,批量去处理js, jsx去转换成需要的ts文件
  • 做一个回退操作, 如果不理想, 用户可以回退为原来的js文件
  • 。。。

ts compile 可以根据大家的需要做一些大家预期的操作,类似于babel的parser, traverse, generator等。这里只是大概的思路提供给大家,毕竟现在很多小程序框架也是类似的方式。happy coding!

转载于:https://juejin.im/post/5d05fdb6f265da1bc23f769d

你可能感兴趣的文章
系列3:WAS Liberty Profile hello mysql jdbc
查看>>
基础知识:python模块的导入
查看>>
Android MVC之我的实现
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
关于批处理-1
查看>>
Tomcat部署Web应用方法总结
查看>>
Python3 django2.0 字段加密 解密 AES
查看>>
CCNA实验之:网络地址转换(NAT)实验
查看>>
计算机网络原理笔记-停止等待协议
查看>>
确定当前记录和下一条记录之间相差的天数
查看>>
sql语句返回主键SCOPE_IDENTITY()
查看>>
机器学习开源项目精选TOP30
查看>>
iOS开发-邮件发送
查看>>
/etc/resolv.conf文件详解
查看>>
【转】VC的MFC中重绘函数的使用总结(整理)
查看>>
JQuery日记_5.13 Sizzle选择器(六)选择器的效率
查看>>
oracle查看经常使用的系统信息
查看>>
Django_4_视图
查看>>
Linux的netstat命令使用
查看>>