我们团队是如何从 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 的的主要内容,如果未能解决你的问题,请参考以下文章