两年前,我的团队正准备对我们的设计系统进行第一次性能重构。我们的计划是最大限度地提高性能,其前提是删除过时的API,这些API经过多年的慢慢积累,现在代表了大量的臃肿和代码重复。令人担忧的是,这样做意味着在采用和稳定性对我们的消费者来说是一个巨大的痛点的时候,会有前所未有的大量的破坏性变化。自动迁移似乎是唯一可行的方法......
就像大多数流行的库,如React、Next.js等,它们都提供了codemods,以帮助其庞大的用户群跨版本迁移,我们需要一个定制的、相当简单的jscodeshift的CLI包装,以提供发布、下载和运行codemods的方法。因此,我们创建了一个自定义的 "codemod-cli",这是我们的第一个内部codemod基础设施。
我们已经编写了一些codemod来帮助一些内部迁移。这些都是以独立的转换文件的形式,与它们所服务的软件包放在一起,但都是一次性的,写完了,运行了,就忘了,因为工作已经完成,不再需要了。这与React和Next.js提供的东西并不一样,没有结构化,但有时不清楚何时以及为什么要使用codemod。这就是我们看到的第一个机会。
如果每一个突破性的变化都与一个版本化的代码模型配对,会怎么样?
在这个模型中,每一个突破性的变化都会伴随着一个codemod,codemod的名字会指向它的目标版本--v2.0.0,所以它的意图对用户来说是清楚的。更重要的是,一旦我们积累了多个主要版本的codemod,落后于许多版本的用户就有可能通过依次运行所有可用的codemod而被弹射到最新的代码。
有了这个范式和在codemod-cli中实现的功能,我们到了需要放下工具的时候了。我们需要回到我们最初要完成的项目上,提高性能。现在,我们要开始通过编写和发布我们的突破性变化的codemod来进行测试。但现在我们有能力真正改变多年来一直阻碍我们的API。为了简短起见,我将跳过接下来的一年多时间,只说。它成功了!我们写了大量的codemod,它们运行了,而且它们肯定做了我们最初想做的事情--耶!我们的项目成功了。
然而,我带着许多未完成的工作离开了那个项目,我觉得在这个领域还有许多未被利用的机会。所以我做了其他工程师没有做过的事情,我开始了一个副业......😅。随着越来越多的设计系统和多包库的融合,我觉得现在是分享我所做的事情的时候了,希望它也能帮助那些和我们情况类似的人。
🚚 CodeshiftCommunity
首先,也是最重要的,如果这个项目没有别的东西,它将只是一个代表我们集体的codemod创作知识的文档网站。对于jscodeshift的新人来说,这显然是一个巨大的差距和入门障碍。一个由各种例子和博文拼凑起来的入职体验是相当令人生畏的。
其次,针对包的特定版本的codemods的策略似乎与DefinitelyTyped的策略惊人地相似,版本化的工件(ts类型定义)支持包的整个生命周期。如果我们能够提供类似的设施,作为一个单一的地方,让codemods能够以一种可控的、结构化的方式被分发、记录和维护,会怎么样呢?
第三,每个使用jscodeshift的人,包括我们自己,都可能在编写/维护一个定制的CLI来解决这个问题。我们将需要把所有的东西联系在一起,变成一个单一的、熟悉的CLI工具。
最后,我的主要目标和我目前正在做的事情是。提供一个类似翻新的机器人,它不仅能进行版本升级,还能自动迁移各主要版本的代码,并提示维护者在绿色CI上进行合并,或者在codemod不能完全迁移的情况下,给他们明确的叫号,让他们介入。
它是如何工作的
一般来说,该库的工作方式是将每个codemod作为自己的包发布到npm,或者从现有的NPM包中搭便车。然后我们的CLI可以从任何地方下载和运行它们。目前,我们还有一个隐藏的好处,那就是可以发布带有依赖关系的codemod,这在普通的jscodeshift CLI实现中是不可能的。
使用NPM大大降低了采用的门槛,因为人们需要做的就是发布他们现有的包,并配上一个codemod.config.js ,这实际上是一个文件,其中包含了codemods的名称和位置。在现有的npm包中,只需添加这个文件就可以采用Codeshift,不需要依赖性。
比如说:
export.module = {
transforms: { // Versioned transforms
'12.0.0': require.resolve('./18.0.0/transform'),
'13.0.0': require.resolve('./19.0.0/transform'),
},
presets: { // Generic utility transforms
'format-imports': require.resolve('./format-imports/transform')
}
};
现在运行上面的codemod只是简单地引用包的名称和版本。
让我们暂时假设这个配置存在于@chakra/button 包中。假设配置和codemods被发布到NPM,人们可以运行:
$ codeshift -p @chakra/button@12.0.0 path/to/src
Codeshift会在本地下载最新版本的@chakra/button ,确保我们始终拥有最新的代码。CLI会读取配置,并将其传递给jscodeshift,在那里会进行正常的转换。
传递--sequence 标志将触发v12和v13的运行,一个接一个。
预设配置,是 "通用 "或 "非特定版本 "的编码方法的地方,它与@chakra/button 有关。这可能是人们想要分享编码方法的地方,比如,format-imports ,它将把按钮导入格式化为某种顺序。运行一个看起来像:
$ codeshift -p @chakra/button#format-imports path/to/src
你如何采用Codeshift由你决定
用codemods增强现有的包,使它们可以通过@codeshift/cli 。
或者创建你自己的私人代码模型注册表,利用相同的文档、最佳实践和结构化的API,同时完全兼容社区。
下一步是什么?
我们的首要目标是降低准入门槛,使整个JS生态系统能够利用这些资源和社区,并反过来为我们所依赖的库生成codemod覆盖,就像我们从DefinitelyTyped获取类型定义一样。我们的想法是,开发者最终能够利用社区集体提供的codemod,简化核心依赖的迁移(react、redux、next、emotion、jest等)。这是一个崇高的目标,但考虑到如果现有的生态系统的codemods与该库集成。
对他们来说,唯一的障碍是提供一个配置文件和使用@codeshift/cli ,这可以安全地与现有的基础设施一起完成。一旦发布到NPM,我们的构建工具和消费者可以从任何地方运行这些代码模型。
最终,更重要的是将该领域的工作整合到一个结构化的库中,为更广泛的JS生态系统服务。
