我们团队是如何从 Javascript 和 Flow 迁移到 TypeScript 的

Posted SHERlocked93

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我们团队是如何从 Javascript 和 Flow 迁移到 TypeScript 的相关的知识,希望对你有一定的参考价值。

作者 | Andrey Frolov

译者 | 叙缘

审校 | 红泥

策划 | 闫园园

这个故事发生在很久以前。我们一生中常常押错宝,但不幸的是,我们对此无能为力。因为我们在做决定时,我们总是基于有限的信息(尤其是关于未来的信息),所以本质上我们的决策更像是在赌博。这就是生活:有时发生在你的个人生活中,有时发生在你的工作生活中。但真正重要的是如何从这个决策过程中学到东西。

押注于 Flow 就是这样一个决策。你有可能还不知道 Flow 是什么,它是 Facebook 推出的 javascript 超集,为 JavaScript 带来了类型和类型系统。它的设计思路非常清晰:使用一种叫做“Flow”的语法糖编写代码,它会获取你的源代码文件,构建扩展抽象语法树(abstract syntax tree,AST),对其进行分析,如果一切正常,则将其转换为常规的 JavaScript 代码。

我们从 2017/2018 年就开始开发我们的应用程序了,当时 TypeScript 的工具、支持和文档还没有现在那么强大,而且 Flow 和 TypeScript 之间还算势均力敌,同时,项目工程的开发人员更熟悉 Flow,所以我们选择了它。转折点出现在 2019 年左右,TypeScript 开始迅猛发展,Flow 却基本上被抛弃了,因此我们决定在某个时候从 Flow 迁移到 TypeScript。Flow 的使用者们面临下面的问题:

  • Flow 有些特性没写文档,只能通过深入 Facebook 的代码库或 Flow 的源代码来了解这些特性。

  • 编译过程太慢,会让计算机崩溃。

  • 对使用者们遇到的问题,Flow 的维护者们能提供的支持非常有限。

  • Flow 放弃了 npm 包的用户端输入模式。

这个列表还可以继续列下去,但受篇幅所限,先列到这里。总之,我们觉得应该回头,换用另一个 JavaScript 超集。如果你问我为什么“另一个 JavaScript 超集”选择的是 TypeScript,下面我分两方面说明下原因:

公司层面的具体原因:

  • 我们的后端已经在使用 TypeScript 了,具备相关专业知识和经验。

  • 我们在后端程序中遵循约定优于配置原则,可以根据规范自动生成不同客户端的 SDK。

  • 我们前端有一个重要组成部分,是基于后端 API 的类似于增删改查(CRUD)的接口。因此,我们更希望将 SDK 用于强类型前端语言。

基于常识的原因:

  • TypeScript 有很棒的文档,还有很多教程、书籍、视频和其他学习资源。

  • 弱类型系统。这点是有争议的,因为它不像元语言(译注:Meta Language,ML)(比如 Reason、Haskell、Ocaml)的类型系统那样全面,反而因此入门门槛较低,很容易理解清楚。

  • 社区支持度高。你能想象到的大多数软件包,TypeScript 都有很好的支持。

迁移从何处开始?

首先添加 tsconfig.json 引入配置:


  // ...
  "compilerOptions": 
    "allowJs": true
  

这个 TypeScript 配置允许我们把 ts 和 tsx 文件添加到系统中。

接下来,再依次启用 ts 各项规则(下图中的选项只是示例——请根据自己的代码库选择适合的选项)。

我们以前用了 Flow,所以我们需要配置一些修复选项,将其添加到.flowconfig 文件中。

module.name_mapper.extension='ts' -> 'empty/object'
module.name_mapper.extension='tsx' -> 'empty/object'

准备就绪,现在可以迁移了。走起!等等,就这么简单?

到哪里才算结束?

后来我们碰到了各种各样的问题,搞出来很多 Flow 文件和 TypeScript 文件,使我们陷入了困境。我们花了几个月的时间才明白这么搞是行不通的。

我们的主要问题是,没有制定一个计划,应该从哪些文件开始、花费多少时间和人力、以及如何结合其他关于组织方面或工程方面的事项。

新的一天,新的尝试

我们认清了代码上的问题及导致的原因后,决定再试一次。

还是得祭出关系图来说明问题。请看下面这张图:

任何工程代码都可以通过一张图清晰的展现出来。所以,做法也很简单:从图的叶子开始迁移,一步步向根部移动。在我们的例子中,叶子节点是样式化组件和 redux 层逻辑。

假设你有一个 sum 函数,它不依赖任何东西:

function sum(val1, val2) 
   return val1 + val2;

转换为 TypeScript 后,它会变成:

function sum(val1: number, val2: number) 
   return val1 + val2;

例如,如果你有一个用 JavaScript 编写的简单组件,你马上就能发现 TypeScript 带来的好处。

//假设我们持续迁移,把jsx文件转换成tsx文件。
import React from 'react'
 
//收益在此! 这里会提示你代码有错误(见下行)
const MySimpleAdder = (val1) => <div>sum("hey", "buddy")</div>
 
// => 不能把'number'类型的数据传递给'string'类型的参数。

那么,下面我们就重新开始吧。

第一步是安装 dependency-cruiser。

然后在命令行执行命令:

depcruise --init

或者手动添加.dependency-cruiser.js 配置。下面我展示一个例子,用于对这种迁移进行全面的概述。

module.exports = 
  forbidden: [],
  options: 
    doNotFollow: 
      path: 'node_modules',
      dependencyTypes: ['npm', 'npm-dev', 'npm-optional', 'npm-peer', 'npm-bundled', 'npm-no-pkg'],
    ,
    exclude: 
      path: '^(coverage|src/img|src/scss|node_modules)',
      dynamic: true,
    ,
    includeOnly: '^(src|bin)',
    tsPreCompilationDeps: true,
    tsConfig: 
      fileName: 'tsconfig.json',
    ,
    webpackConfig: 
      fileName: 'webpack.config.js',
    ,
    enhancedResolveOptions: 
      exportsFields: ['exports'],
      conditionNames: ['import', 'require', 'node', 'default'],
    ,
    reporterOptions: 
      dot: 
        collapsePattern: 'node_modules/[^/]+',
        theme: 
          graph: 
            rankdir: 'TD',
          ,
          modules: [
            ...modules,
          ],
        ,
      ,
      archi: 
        collapsePattern:
          '^(src/library|src/styles|src/env|src/helper|src/api|src/[^/]+/[^/]+)',
      ,
    ,
  ,
;

把它用起来——这个工具有很多成熟的预设配置和很棒的文档。

工具

Flow to ts——可以将 Flow 源文件原生地转换为 TS 源文件,减少体力劳动。

TS Migrate——可以将 JS 文件转换为 Typescript 文件。这里有一篇很棒的介绍文档。

分析和衡量标准

根据一些实践经验,我们提出了跟踪迁移进度的想法。我们会向其他团队和利益相关者提供透明的信息,以帮助衡量迁移效果,这让整个过程拥有了游戏化的魔力。

在技术层面,我们仅通过手动更新 excel 表格和用 bash 脚本,来计算每次推送到主分支时的 JavaScript/Typescript 文件数,并将结果发布到 Slack 频道。下面是 bash 脚本的基本示例,你可以根据需要修改配置:

#!/bin/bash


REMOVED_JS_FILES=$(git diff --diff-filter=D --name-only HEAD^..HEAD -- '*.js' '*.jsx')
REMOVED_JS_FILES_COUNT=$(git diff --diff-filter=D --name-only HEAD^..HEAD -- '*.js' '*.jsx'| wc -l)
 
echo $REMOVED_JS_FILES
echo "$REMOVED_JS_FILES_COUNT//[[:blank:]]/"
 
if [ "$REMOVED_JS_FILES_COUNT//[[:blank:]]/" == "0" ]; then
echo "No js files removed"
exit 0;
fi
 
JS_FILES_WITHOUT_TESTS=$(find ./src  -name "*.js*" ! -wholename "*__tests__*" -print | wc -l)
JS_FILES_TESTS_ONLY=$(find ./src  -name "*.js*" -wholename "*__tests__*" -print | wc -l)
TOTAL_JS_FILES=$(find ./src  -name "*.js*" ! -wholename "*.snap" -print | wc -l)
 
JS_SUMMARY="Total js: $TOTAL_JS_FILES//[[:blank:]]/. $JS_FILES_WITHOUT_TESTS//[[:blank:]]/ JS(X) files left (+$JS_FILES_TESTS_ONLY//[[:blank:]]/ tests files)"
 
PLOT_LINK="<excellink>"
 
echo $JS_SUMMARY
 
 
AUTHOR=$(git log -1 --pretty=format:'%an')
MESSAGE=":rocket: ULTIMATE KUDOS to :heart:$AUTHOR:heart: for removing Legacy JS Files from AGENT Repo: :clap::clap::clap:\\n\\`\\`\\`$REMOVED_JS_FILES\\`\\`\\`"
 
echo $MESSAGE
echo $AUTHOR
 
SLACK_WEBHOOK_URL="YOUR_WEBHOOK_URL"
SLACK_CHANNEL="YOUR_SLACK_CHANNEL"
curl -X POST -H 'Content-type: application/json' --data "\\"text\\":\\"$MESSAGE\\n$JS_SUMMARY\\n\\n$PLOT_LINK\\",\\"channel\\":\\"$SLACK_CHANNEL\\"" $SLACK_WEBHOOK_URL

给代码库“止血”

迁移是一个过程,一天做不完。大家都非常优秀,但也都有自己的习惯,也会感到疲倦等等。这就是为什么当迁移后,大家还是会习惯性地按旧方式做事情。因此,当工程师继续编写 JavaScript 而不是 Typescript 时,你需要找到一种方法,来给代码库“止血”,即让它停止产生新的需要修补的地方。可以用自动化工具来达成这一目标。最好的办法是编写一个脚本来检查代码库有没有添加新的 JavaScript 文件。第二种办法是使用与测试覆盖相同的思想,将其理念用于测试类型。

这里有一个有用的工具可以满足需求。

设置一个阈值,添加一个 npm 脚本就可以了:

"type-coverage": "typescript-coverage-report -s --threshold=88"

成果

现在我们已经成功地将超过 20 万个 JavaScript 文件迁移到 TypeScript,我们的代码库中已经没有 Flow 或 JS 代码了,而且我们用 TypeScript 用得非常愉快。

查看英文原文:

https://dev.to/frolovdev/how-we-migrated-from-javascript-and-flow-to-typescript-at-osome-4661

以上是关于我们团队是如何从 Javascript 和 Flow 迁移到 TypeScript 的的主要内容,如果未能解决你的问题,请参考以下文章

从老板到项目成员,如何从燃尽图中洞悉团队工作?

从老板到项目成员,如何从燃尽图中洞悉团队工作?

我们是如何将3万代码从Flow移植到TypeScript的?

如何防止其他用户/团队成员从根项目执行少量命令?

如何搭建核心骨干团队---转

关于一个C语言写的代码无法运行出结果