作者 | Dusty Phillips
译者 | 刘志勇
策划 | Tina
导读:在软件开发的大潮中,重写项目常常被视为一项既常见又充满挑战的任务。本文作者结合自身多年的实战经验,深入剖析了前端与后端重写之间的异同,并特别分享了从 React 向 Svelte 迁移的历程,其中遇到的种种难题与收获均一一呈现。通过对比 Svelte 与 React 在性能、开发速度及开发者满意度等方面的表现,作者认为 Svelte 具有成为新项目首选框架的潜力,并分享了自己对 Svelte 的独特见解与热切期待。此外,文章还着重强调了项目重写的必要性及其所面临的挑战,同时列举了一些成功的重写案例与失败的教训。若你对软件重写、前端框架的选择以及 Svelte 的优势抱有浓厚兴趣,那么本文定能为你带来深刻的见解与启发。
我和妻子一起倾注心血于 Fablehenge,这款专为小说作家打造的写作应用已经陪伴我们好几个年头了。最初,这不过是个业余爱好,我们会用几个周末的时间全力以赴,然后让它暂时休息几个月,偶尔在晚上的空闲时间里稍作调整。
今年年初,我本打算好好休个长假,去开辟新的徒步小径、做做木工活。虽然这些活动确实进行了,但我发现自己还是基本上把主要时间都投入到了 Fablehenge 的工作中。
Fablehenge 是用 React 编写的,我和 Jen 都对 React 了如指掌。我早在它还未开源时就断断续续地开始使用,而 Jen 则多年来一直只用它一个。
一月份,我大部分时间都在努力让一个相对(但并不算特别)复杂的拖放系统正常运作。接着,Jen 又在二月份花了三周时间来微调和改进我的实现。
我们的代码是基于 dndkit 构建的,就像我找到的每一个 React 拖放库一样(我为此花了几个小时搜索),它最近都鲜有维护更新。尽管我很喜欢这个库,但经过我们共同花费大约六个星期的调整后,我们发现应用存在性能问题,而自己却束手无策。
在探索解决方案的道路上,我意外地发现了 svelte-dnd-action 这个库。当时,它最新的提交竟然就在 18 小时前!天哪,我真是羡慕不已!
虽然之前听说过很多人对 Svelte 的热爱,但我总觉得那更多是炒作而非实质。毕竟,Javascript 社区向来以追求 “新奇特” 而著称。
然而,为了从 React 的挫败感中解脱出来,我还是决定花一天时间学习 Svelte 的教程,并尝试创建了一些简单的应用。当我测试 svelte-dnd-action 时,真的被它深深吸引了。我立马给 Jen 分享了教程链接,没过多久她就告诉我:“我觉得我再也不想回到 React 了。”
于是,抱着玩玩看的心态,我开始将几年的工作成果迁移到 Svelte 上。这个过程竟然异常顺利,我原本还担心会出现什么问题。在我的职业生涯中,遇到过许多看似很好的库,但最后却带来了更多麻烦而非解决方案。抽象层总是会出问题,这是难免的。但随着对 Svelte 的深入了解,我渐渐意识到它真的很特别。
我记得不知道什么时候曾听人说过,新技术要想克服用户社区转换的惰性,必须比现有技术好上 10 倍才行。React 出现时,其开发体验显然比当时的竞争对手(如 jQuery 和早期的 Angular,当时的 Angular 与今天的 Angular 不同)要好得多,远远超过了 10 倍的标准。因此,整个社区几乎一夜之间都转向了 React。
虽然之前我对 Svelte 的炒作持保留态度,认为它不太可能成为改变整个社区的 10 倍改进技术。但现在我的想法已经变了。
我估计我们在 React 应用上投入了大约一年的总人时。我们的代码库写得既规范又易于维护,规模适中,不存在任何未知问题让我们担忧。因此,相较于最初的投入,从头开始重写它花费的时间肯定会少很多。我们深知复杂性的根源所在,于是优先解决这一问题。同时,我们也对应用程序进行了结构化调整,以便能够复制粘贴大约百分之二十的与查询和修改数据相关的代码,只需稍作修改即可。
整个重写过程仅花了三个星期的时间。这里所说的 “重写”,并非指 “大部分重写”,而是指 “完全重写”。2 月 26 日完成了初始提交,到 3 月 15 日,Svelte 应用已经部署并投入生产。我们已经开始开发新功能,并且进展迅速。
请回想一下,之前 Jen 和我在 React 中耗费了三周时间才勉强搞定拖放系统。鉴于这是当时面临的主要问题,我们自然在 Svelte 中也优先解决这一问题。而我在 不到一天 的时间内就彻底解决了它。虽然我在 svelte-dnd-action中发现了一个错误或缺失的功能,但维护者在我发布了可靠的重现后两天内就迅速修复了。
若将每六周的 React 开发时间与一天的 Svelte 开发时间相对应,那么我们将在半天内就赚回重写应用程序所花费的时间。当然,这种说法可能稍显(更为)乐观!但不可否认的是,我们的 Svelte 开发速度确实比 React 开发快两倍以上。
虽然 React 相比很多前辈技术有所进步,但它仍然需要编写相当多的样板代码(哪怕是不使用 Redux 的情况下,我们也知道要避开它)。我们已经使用 React 很长时间了,以至于对样板代码已经习以为常;在编写 React 代码时,往往会忽略每个组件中重复的部分。
我们的 Svelte 应用程序只用了 React 应用程序所需代码的 60%。这里我要再次强调,我们的 React 应用程序编写得非常规范,没有多余的代码或未使用的功能。我们几乎把所有东西都迁移到了 Svelte 上。当然,在迁移过程中我们也对一些功能进行了重新设计,但这只是因为这样做起来很容易。要说的话,我怀疑我们的 Svelte 代码是写多了,不是写少了。
我应该承认,代码量减少的部分原因是我们现在使用了 tailwind CSS,而之前我们使用的是 Chakra。tailwind 在减少代码行数方面确实非常出色。关于 tailwind 的更多细节,我会稍后详细介绍(提前透露一下:我们其实不太喜欢它)。
Fablehenge 是一个业余项目,我们的初衷是享受在上面的工作时光(当然也希望作家们能喜欢使用它)。我们本以为自己很喜欢 React,但事实是,Jen 和我经常在午餐时抱怨 React 生态系统的各个方面。并不是说 React 不好,它真的很棒!只是相较于它的潜力,使用它的乐趣似乎没有那么大。
相比 React,Svelte 有趣得多,而且我认为等到 Svelte 5 发布(即将)时,它会更有趣。当然,享受编码的感觉是非常主观的,但 Svelte 完全符合我们对它的所有期待。
我们是一个两人团队,可以完全掌控项目。因此,我无法断言 Svelte 是否能像 React 那样,扩展到拥有数千名开发人员和数百万行代码的公司规模。但直觉告诉我,它是可以的。Svelte 与 React 鼓励的组件模型和分隔样式保持了一致。然而,为了效率,它也为开发者提供了很多强大的功能,但滥用这些功能可能会导致维护困难。
最值得一提的是,Svelte 强调单向绑定,但在适当的情况下也允许双向绑定(通常是在表单元素中)。遗憾的是,关于 “适当” 的情况并没有硬性规定。我完全能想象到,大公司的实习生可能会觉得双向绑定更简单,但这实际上可能会给未来的维护带来噩梦。但任何有经验的开发者都会告诉你,审查实习生的代码比自己编写代码需要花费更多的时间和精力,所以我并不认为这些不良习惯会真正合并到项目中,对吧?
总的来说,项目的代码越少,维护起来就越容易。这也是为什么会有更高级的语言和框架存在的原因。如果 Svelte 能够稳定地将维护代码量减少 30-40%,那么我期望它也能用到大规模项目里。
尽管 Svelte 实际上是一种新的编程语言,但它非常依赖 JavaScript、HTML 和 CSS,因此如果你懂得 Web 开发,那么 Svelte 对你来说并不陌生。当然,也有一些独特之处需要你去学习,尤其是在响应性方面。不过,令我们惊讶的是,在阅读了几个小时的教程后,我们在 Svelte 中的生产力很快就得到了提升。
Svelte 立刻就显得非常合理。它给人的感觉就像是充分利用了标准技术,而不是像许多框架那样重新实现它们。
事实上,学习起来如此轻松,以至于我经常觉得我是在“回忆”如何使用 Svelte,而不是重新学习。我会感叹:“噢!这才是 Web 开发应有的样子。我都忘了!”
这次重写能够如此迅速且简单地进行,一个重要原因在于我们拥有一套非常完善的测试套件以及一个详尽的电子表格,其中详细列出了应用程序中的每个功能及其手动测试方法。这些工具的重要性简直无法用言语形容,我此前从未在真正的公司中见识过如此完备的测试体系。
我们的测试套件完全采用 Cypress 编写,主要用于端到端(e2e)测试。当然,我们也包含了一些单元测试,但这些测试同样是在 Cypress 运行器中实现的,主要是因为我不想为 testing-library 额外设置持续集成(CI)。
这里稍微提一下,我们对 Cypress 并非完全满意。但在我们选择它的时候(几年前),它是唯一一个能与 contenteditable 元素稳定协作的工具。由于我们是一个小说写作平台,因此 contenteditable 元素的使用相当频繁。Cypress 的表现还算不错,所以我们最近并没有尝试其他替代方案。
不过,Cypress 测试并不能直接无缝迁移到我们的 Svelte 应用程序中。这主要是因为我们在迁移过程中并未总是将 data-cy 属性一并移植,同时某些在 React 应用程序中适用的选择器在 Svelte 中并不直接兼容。但经过一些轻微的调整,我们很快完成了迁移工作。在调试 Cypress 不稳定性的过程中,我们积累了大量经验,这使得迁移过程相对顺利。
有一点我想提一下,那就是在 Svelte 应用程序中,我们不得不在测试中增加了更多的 cy.wait 调用。据了解,Svelte 的响应模型是这样的:即使页面上的某个元素已经更新了内容,也并不意味着所有依赖该更新内容的其他元素都已经接收到新值。
以我们的大纲测试为例,添加新章节或场景后,它们可能会立即显示在大纲中,但操作选定场景的工具栏按钮却可能无法立即识别出这些新添加的项目。因此,我们不得不在那里添加一个 cy.wait(50) 来确保元素状态同步。这多少有些令人无奈。我希望 Svelte 5 能够解决部分这类问题,否则我们可能需要在可点击元素中添加额外的 data- 属性来传递那些原本应由响应系统处理的数据。
虽然目前的情况还有改进的空间,但重要的是,我希望你能够认识到一点:如果你打算迁移或重写项目,那么务必在原始项目中构建一套全面且易于迁移的测试套件。这是确保迁移工作顺利完成并验证新版本正确性的关键所在。
我们的 React 应用原本是一个单页面应用。然而,Sveltekit 在支持服务器端渲染、预渲染(静态站点)以及客户端渲染方面表现出色,它甚至允许我们在站点的不同部分混合使用这些渲染方式。
起初,我们选择了服务器端渲染来构建 Svelte 应用,因为我认为这是 Sveltekit 中备受推崇且支持度较高的方法。然而,鉴于我们应用程序的特定结构,我们并未从服务器端渲染中看到太多明显的优势。我们并不进行直接的网络请求,而是将所有数据存储在 IndexedDB 中,并依赖出色的 Dexie Cloud 服务来处理繁重的数据操作。
因此,我认为维护和扩展一个单独的生产节点服务来运行 Sveltekit 的服务器端渲染功能,其开销并不比直接在边缘部署静态站点的简单性来得实用。在使用 Svelte 的过程中,我们并未明显感受到服务器端渲染与单页面应用之间的性能差异,尽管这两种方式都比 React 应用的性能要好得多的多。
说实话,我从未对服务器端渲染的理念有过太深的印象,因此这个决定并没有花费我太多精力。将计算从客户端移至服务器并不总是能带来显著的好处,特别是考虑到浏览器在渲染 HTML 和运行 JavaScript 方面已经做了非常出色的优化。
顺便提一下,最近我看了一个演讲,其中一位开发者认为他有一个 “绝妙” 的想法,即在 onMount 处理程序中渲染内容以加快服务器端渲染的速度。听到这种将计算推送到客户端的做法,我只能摇头叹息。
当然,服务器端渲染在某些情况下确实有其用武之地,特别是当涉及到高度动态或用户生成的数据,并且需要进行 SEO 优化时(例如电子商务网站)。然而,对于搜索引擎无法索引的登录保护数据,我认为服务器端渲染并不具备太多优势。它似乎是在优化错误的方向。
鉴于 Svelte 无论采用哪种渲染模式都表现得非常迅速,我并不打算在此问题上过多纠结。特别是考虑到 Svelte 5 承诺将为我们带来更高的性能,我猜测使用 Svelte 5 进行客户端渲染将足够快速,适用于大多数场景。而且,我真的很喜欢提供静态 HTML 和 JavaScript 文件所带来的最小化运维开销。
Svelte 的响应性确实需要一些时间去适应,主要是因为它的响应性很难被关闭。经过一个星期的使用,我才逐渐适应,并确信它不会在我不希望的时候自动更新。虽然这有时候比该更新的时候不更新要好,但在 Svelte 4 中,想要关闭响应性却不太直观。
据说,Svelte 5 已经解决了这些问题。有了符文和 untrack 函数,确保响应性在正确的时间和位置发生变得轻而易举,而不再是不受控制的。此外,Svelte 5 更加易学易用,因为它相较于 Svelte 4 减少了概念和自定义语法的数量,但更有效地复用了这些概念。更值得一提的是,Svelte 5 的一些新语法与 React 更为相似,因此如果你正在从 React 迁移,Svelte 5 会比 Svelte 4 更容易上手。
如果有人考虑将 React 重写为 Svelte,我可能会建议先等待 Svelte 5 的普及。虽然它现在仍处于预发布阶段,但在我们的情况下,我们急需尽快发布生产应用程序(尽管 “尽快” 可能只是 “三周”)。因此,我们不愿依赖 alpha 软件,尤其是当我们对 Svelte 还不是很熟悉时。在学习一个新框架的过程中,当遇到的错误既可能是因为自己的误解,也可能是因为框架本身的问题时,会面临诸多挑战。
另外,我们不想选择 Svelte 5 的另一个原因是其库生态系统尚未完成迁移。虽然 Svelte 5 应与 Svelte 4 完全兼容,但它也预设你会逐渐迁移到新范式上。即使现有的 Svelte 4 库与 Svelte 5 完全兼容,我更期待那些从头开始设计或从当前版本重新设计以充分利用 Svelte 5 优势的库。
每个库的选择都伴随着权衡,这确实是一个无法回避的现实。Svelte 在多数方面表现得相当出色,然而,样式化子组件却是一个令人不甚满意的领域。
Svelte 具备一些出色的功能,允许我们独立地为单个组件进行样式化。然而,一旦涉及到为第三方组件(如来自组件库的组件)添加样式时,情况就变得复杂起来。我们不得不采取一些不那么正规的方法,要么退而求其次使用全局样式,要么依赖于库来接受某种类或属性字符串。
因此,我们发现,在样式化方面,几乎每一个 Svelte 组件库都让人头疼(或许 melt-ui 是个例外)。其中大多数组件库都依赖于 tailwind-css,并通过传递类名来应用样式。但问题在于,你必须清楚组件所使用的类名,才能确保你正在修改正确的元素。更糟糕的是,如果组件结构复杂,你还需要根据正在样式化的子组件传递不同的类名(这取决于所使用的框架),这就要求你必须深入了解库的内部结构才能进行样式化。
我们从 flowbite-svelte 开始尝试。这个库确实令人惊艳,我们能够在短时间内取得显著的进展。然而,最后的 20%(即样式化部分)却成了我们的拦路虎。虽然不能说这个过程是痛苦的,但也绝非愉快。也许我们并不讨厌它,但我们也并不喜欢它,而我们真心希望我们的应用是能让自己喜欢的。
Tailwind 有时被形容为 “爱恨分明”。一开始我们并不介意它,但随着时间的推移,我们开始对它产生了反感,所以看来我们属于 “恨” 的那一极。虽然这并无大碍,但经过深入研究,我们认为,在 Svelte 中,使用 Tailwind 或其类似工具来样式化子组件是目前最为合理的选择。
当然,最好的替代方案可能是完全不使用组件库,这意味着我们需要将组件复制粘贴到自己的设计系统中,并自行进行样式化。即使我们选择了这条路(或许还会结合使用 melt-ui),我们仍然需要仔细考虑如何将适当的 props 或类名传递给我们的组件,以确保它们符合设计要求。
另一个选择是更加自由地使用全局样式。对于小型应用程序来说,这可能是一个可行的方案,但我们都清楚,长期来看这并非可扩展的解决办法。
我相信,随着 Svelte 5 的 Snippets 功能的推出,这个问题将得到一定程度的缓解,但恐怕无法完全解决。在 Svelte 的问题跟踪器中,我们可以看到许多关于这个问题及其潜在解决方案的讨论。因此,我期望一旦 Svelte 团队完成了庞大的 Svelte 5 重写工作,他们将有更多精力专注于解决样式化问题,为 Svelte 带来与其他方面同样优雅和简洁的体验。
关于是否应从 React 迁移到 Svelte,我确实无法给出一个明确的答案。但对我们而言,Svelte 似乎是个潜在的竞争优势,毕竟它能让我们以更少的人力实现更快的开发速度。然而,如果你的应用和我之前接触的许多 React 应用类似,即缺乏足够的测试,充斥着死代码路径或过时的代码路径,且无人真正了解,那么迁移起来确实会非常棘手。虽然从长期来看,这有助于提升项目的健康度,毕竟死代码路径会被清理,误解的代码路径也会变得明朗,但长期健康的维护往往伴随着众多彼此争抢资源的优先事项,如新功能开发、满足用户需求和应对日常的运维挑战。若你只有 18 个月的时间窗口,那么规划三年后的维护工作并无太大意义。
我猜测,大多数生产项目可能不会像我们所预期的那样迅速完成迁移。不过,抛开虚假的谦逊,我们确实是一支实力强劲的开发团队,因此我对我们 React 应用的迁移前景充满信心。
但话说回来,如果你的应用并未达到轻松迁移至其他技术的状态 —— 比如测试不足、非端到端的测试、临时性的 API、不完整的或不可维护的文档,以及使用了团队从未完成迁移的已废弃库或范式等 —— 那么,你恐怕也难以自信地为其添加新功能,无论你如何自我安慰。尽管重写的过程可能会很痛苦,但这也是一个绝佳的机会来整理你的 “代码之家”。
在我的职业生涯中,我见证了众多重写成功的案例,也目睹了不少失败的尝试。通常,前端重写的效果要比后端重写更为理想,因为数据迁移的复杂性相对较低,有时甚至不是问题。成功的重写往往源于这样的立场:“我们已经将应用简化到了极致,现在是重写它的最佳时机。” 而失败的案例则往往沿着 “这个应用太复杂了,让我们推倒重来” 的路线。
我坚信,Svelte 在性能、开发速度和开发者满意度方面的卓越表现,使其相较于 React 有着十倍的优势。因此,它完全有可能成为新项目的首选框架,取代现有的主流选择。当然,我并不是说 React 会消失(毕竟现在还有招聘 jQuery 开发者的岗位,尽管 HTML 标准已经有效地取代了它),但 Svelte 确实有可能在新项目中比 React 更受欢迎。
特别是当你从零开始启动一个全新项目时,我相信你会更倾向于选择 Svelte。
至于那些将 Svelte 评为 “最受喜爱” 框架的头条新闻,嗯…… 我承认,起初我只是把它们当作噪音忽略了。但下一次调查出现时,我一定会积极参与,坚定地站在 Svelte 的阵营中为它摇旗呐喊。
作者简介
Dusty Phillips,加拿大作家兼软件开发者。
原文链接:告别 React,拥抱 Svelte:21天重写应用,开发速度翻倍代码量减半!_架构/框架_Dusty Phillips_InfoQ精选文章