Osome 是如何从 Javascript 和 Flow 迁移到 TypeScript 的

2025-06-05

Osome 是如何从 Javascript 和 Flow 迁移到 TypeScript 的

这个童话故事由来已久。人生中,你常常押错注,但不幸的是,你对此无能为力。你在做决定时,总是基于有限的信息(尤其是关于未来的信息),所以本质上你是在赌博。这就是生活:有时发生在你的个人生活中,有时发生在你的工作生活中。但真正重要的是你如何从你的赌注中学习。

其中一个决定就是押注 Flow。如果你还不知道 Flow 是什么,它是 Facebook 的 JavaScript 语言超集,为 JavaScript 带来了类型和类型系统。它的理念非常简单:用一种语法糖 Flow 编写代码,它会获取你的文件并构建一个扩展的抽象语法树 (AST),对其进行分析,如果一切正常,就将其转换为常规 JavaScript。

虽然我们在 2017/2018 年开始开发应用程序,当时 TypeScript 的工具、支持和文档不如现在强大,Flow 和 TypeScript 之间也没有明显的优胜者。最初开发它的开发者对 Flow 更为熟悉。转折点出现在 2019 年左右,当时 TypeScript 的发展速度更快,Flow 基本上被抛弃了,我们也决定在某个时候迁移到 TypeScript。Flow 用户将面临以下问题:

  • 未记录的功能,只有深入研究 Facebook 的代码库或 Flow 源代码才能了解
  • 编译过程缓慢,会损坏你的电脑
  • 维护人员对于您的问题和困难提供的支持非常薄弱
  • 放弃 npm 包的用户空间类型

这个清单还可以一直列下去,但不知过了多久,我们突然想到应该过渡到 JavaScript 的另一个超集。如果你问我为什么,原因有几个,可以分为两部分:

公司特定原因:

  • 我们已经在后端使用了 TypeScript,并且具备所需的专业知识和经验。
  • 我们已经实现了合约优先的后端编程。不同客户端的 SDK 会根据规范自动生成。
  • 我们的前端很大一部分是基于后端 API 的 CRUD 类接口。因此,我们希望为前端使用具有强类型支持的 SDK。

基于常识的原因:

  • 出色的文档、许多教程、书籍、视频和其他内容。
  • 弱类型系统。它存在争议,因为它不像ML类型系统(Reason、Haskell、Ocaml),因此任何背景的人都可以轻松地理解它。
  • 社区的广泛采用,您能想到的大多数软件包都具有出色的 TypeScript 支持。

它是如何开始的?

据我所知,迁移是通过添加 tsconfig.json 开始的:



{
  // ...
  "compilerOptions": {
    "allowJs": true
  }
}


Enter fullscreen mode Exit fullscreen mode

这个 TypeScript 标志允许我们开始向我们的系统添加文件tstsx

接下来,我们开始逐一启用 ts 规则(以下选项仅供参考 - 选择适合您代码库的选项)

图片描述

但是我们已经有了 Flow,所以让我们修复它并添加到.flowconfig



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


Enter fullscreen mode Exit fullscreen mode

现在,您已准备好开始迁移。开始吧!等等,就这么简单?

我们最终到了哪里?

由于各种原因,我们最终陷入了困境。我们有大量使用 Flow 和 TypeScript 编写的文件。我们花了几个月的时间才意识到这样做不行。

这里的主要问题之一是我们没有计划应该从哪些文件开始,需要花费多少时间和容量,以及其他组织和工程问题的组合。

另一天,再试一次

在接受了我们的代码库健康状况不佳的事实后,我们最终决定再次尝试。

这又是图表发挥作用的时候了。看看这张图片:

图像

任何代码库都不过是一张图。所以,思路很简单:从图的叶子节点开始迁移,然后向图的顶部移动。在我们的例子中,迁移的是样式组件和 Redux 层逻辑。

图像

假设你有sum一个函数,它没有任何依赖项:



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


Enter fullscreen mode Exit fullscreen mode

转换为 TypeScript 后,您将获得:



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


Enter fullscreen mode Exit fullscreen mode

例如,如果您有一个用 JavaScript 编写的简单组件,当您转换该特定组件时,您会立即获得 TypeScript 带来的好处。



// imagine we continue the migration and convert the file form jsx to tsx
import React from 'react'

// PROFIT! Here's the error 
const MySimpleAdder = ({val1}) => <div>{sum("hey", "buddy")}</div>

// => Argument of type 'string' is not assignable to parameter of type 'number'.


Enter fullscreen mode Exit fullscreen mode

因此,让我们为您当前的项目创建另一张图片。

第一步是安装dependency-cruiser

使用 CLI



depcruise --init


Enter fullscreen mode Exit fullscreen mode

或者.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/[^/]+/[^/]+)',
      },
    },
  },
};


Enter fullscreen mode Exit fullscreen mode

试用一下——该工具有很多准备好的预设出色的文档

工具

Flow 到 TS — 帮助生成从 Flow 到 TS 文件的原始迁移,并减少 monkey job。TS
迁移— 帮助将 JS 文件转换为 Typescript 文件。这里有一个很棒的介绍

分析和测量

这并非我的主意,但我们想到了跟踪迁移进度。在这种情况下,游戏化就显得尤为重要,因为它可以为其他团队和利益相关者提供透明度,并有助于衡量迁移的成功程度。

图片描述

让我们深入探讨一下这个主题的技术细节。我们刚刚手动更新了 Excel 表和 Bash 脚本,该脚本会在每次推送到主分支时计算 JavaScript/Typescript 文件的数量,并将结果发布到 Slack 频道。这是一个基本示例,您可以根据自己的需求进行配置:



#!/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

Enter fullscreen mode Exit fullscreen mode




停止代码库的流失

迁移是一个过程,不可能一蹴而就。人都是很棒的,但同时也有习惯,会感到疲惫等等。这就是为什么当你开始迁移时,人们会倾向于按照旧的方式做事。所以,你需要找到一种方法,防止工程师继续使用 JavaScript 而不是 Typescript 时代码库的流失。你可以使用自动化工具来实现这一点。第一个选择是编写一个脚本,检查是否有新的 JavaScript 文件添加到你的代码库中。第二个选择是使用与测试覆盖率相同的思路,并将其用于类型。

有一个有用的工具可以满足您的需要。

设置阈值,添加 npm 脚本,即可开始:



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

Enter fullscreen mode Exit fullscreen mode




结果

今天,我们的代码库中没有 Flow 或 JS 代码,我们很高兴选择使用 TypeScript,并成功将超过 200k 个 JavaScript 文件迁移到 TypeScript。


欢迎随时提问、表达任何意见或顾虑,或讨论你的观点。分享、订阅,创造代码,而不是战争。❤️

如果您发现错误,我很乐意修复它或向您学习 - 请告诉我。

如果您想与我一起工作,您可以在这里申请,或者在TwitterLinkedIn上给我发直接消息

文章来源:https://dev.to/frolovdev/how-we-migrate-from-javascript-and-flow-to-typescript-at-osome-4661
PREV
Hyperscript——React 的隐藏语言
NEXT
我创建了 LinkedIn 帐户😄