在 Remix,我们深知进行主要版本升级有多么痛苦。特别是对于你的应用程序来说,像框架或路由器这样基础的东西。我们希望尽最大努力为你提供一流的升级体验 —— 让我们来谈谈“未来标志”。
每个1框架(或库)在某个时候都必须引入破坏性更改。这些更改会导致你的代码按照今天的写法在新版本上崩溃。这可能会导致构建时(甚至更糟糕的是,运行时)错误。但这些更改是好的!这是我们的框架如何演进、变得更快、采用新的平台特性、实现社区驱动的功能请求等等的方式。
由于这种对破坏性更改的固有需求,出现了 语义版本控制 (SemVer) 规范,该规范定义破坏性更改会指示一个新的主要版本发布。这很棒,因为它让应用程序开发人员知道他们何时应该期望他们的代码在升级时需要更改,而不是他们何时应该期望升级“正常工作”。但请记住,你始终应该阅读发行说明,而不仅仅是盲目升级😉。
恰好,在我开始撰写本文的同一天,@devagrawal09
发推文 如下,引发了关于框架当前状态及其处理“重大重写”的相关讨论。
从讨论中可以清楚地看出,人们对“重大重写”的理解各不相同,并且多年来,框架在这方面的成功程度也各不相同。事情之所以不那么明确,部分原因是虽然 SemVer 提供了一种方法来沟通何时存在破坏性更改,但我们没有类似的、关于如何在我们的框架中引入破坏性更改并将它们传达给应用程序开发人员的商定流程。
一般来说,主要 SemVer 版本的最低标准是一组发布说明,其中概述了主要版本中的破坏性更改。理想情况下,这些还包括关于如何更改代码以采用破坏性更改的说明。但这真的就这些了 —— 除此之外,关于如何最好地准备和帮助用户跨主要版本采用破坏性更改的标准化程度非常低。
因此,多年来我们看到了各种不同的方法,包括但不限于
我们已经看到了效果很好的方法,也有一些效果不好的方法。但是,在成功案例中似乎存在一个共同的概念,那就是为应用程序开发人员提供迭代升级其应用程序的路径。在规模上,无法迭代升级应用程序的各个部分会变得有问题。你最终会得到一个长期存在的 version-N-upgrade
分支,一些工程师会不遗余力地定期将其 rebase 到最新的 main
分支上,并可能在这个过程中一点一点地抓破自己的头发。
这些长期存在的功能分支也往往进展缓慢。我们的利益相关者不希望为了升级我们的堆栈(对客户不可见)而停止几周的功能开发 —— 他们希望并行地不断发布新功能。因此,团队不仅只将一部分精力分配给升级,而且还在处理新旧世界之间固有的上下文切换。这导致升级速度甚至更慢。
如果我们看一下上面的一些方法对应用程序开发人员的影响,我们经常看到它们都涉及某种形式的长期存在的功能分支,这会带来上面提到的缺点。在所有情况下,功能分支的生命周期都取决于破坏性更改的数量,但即使只有少数破坏性更改 —— 在大型代码库中解决这些更改也可能需要一些时间。
迁移指南 通常在功能分支中遵循和实现。
准备版本 倾向于将工作分为 2 个功能分支 —— 一个用于升级到准备版本,另一个用于升级到主要版本。这是一种稍好一点的方法,但这些单独的分支仍然存在相同的缺点。
迁移构建 和/或向后兼容性标志在消除长期存在的功能分支方面做得更好,但它们仍然存在 2 个不理想的方面。首先,它们存在一些潜在的技术风险,因为并排运行两个包(v2 和 v2 “向后兼容”)与运行 v2 完全不同 —— 因此,包的相互通信中存在一个非零的 bug 出现区域。其次,也是可能更重要的,它们仍然会一次性向你转储所有新功能(和破坏性更改)。你通常很少能提前做些什么来准备你的代码库进行升级并减轻影响。一旦 v2 发布,你有可能通过升级到新版本和向后兼容性包来避免长期存在的功能分支。但是,当你迭代地采用破坏性更改并最终删除兼容性构建或向后兼容性标志时,你会在主分支上赶上一段时间。
我们对这些方法都不满意,并希望我们可以提供更平稳的主要升级路径。
当我们第一次开始讨论如何处理 Remix 的破坏性更改时,我忍不住回想起我观看 Yehuda Katz 在 Philly ETE 2016 上做的 无停滞的稳定性 演讲。我不是 Ember 开发人员,但那次演讲给我留下了深刻的印象2,关于框架如何通过使用功能标志来减轻用户采用新功能的痛苦。然而,Ryan Florence 是 一名活跃的 Ember 开发人员,所以当我提到这个演讲时,他立即就知道了“无停滞的稳定性”这个短语。
在我的职业生涯后期,在一个 Vue SSR 应用程序上工作时,我们正在为 Vue 2 -> 3 升级做准备,我很高兴看到他们在他们的构建中引入的 功能标志(尽管我在执行升级之前换了工作,所以我不知道它进展得有多顺利)。
我们在 Remix 知道,如果我们想为用户提供平稳的升级体验,那么功能标志的概念是至关重要的。但是我们想比我们以前在 OSS 中做得更好。即使在上面使用向后兼容性标志的最佳方法中 —— 开发人员仍然会面临主要版本中“一次性获得所有新内容”的情况 —— 这让他们需要在一段时间内赶上进度。此外,这也将所有 v2 代码更改一个接一个地堆叠起来,为你提供了一个潜在微妙错误的压缩表面积。我们想看看我们是否可以做得更好。
在 Remix,我们在主要版本中引入破坏性更改的目标有两个:
换句话说,我们看到的大多数方法都试图在 v2 发布后为你提供从 v1 到 v2 的退出坡道。相反,Remix 的目标是在 v1 版本中发布时为你提供大量通往最终 v2 功能的小入口坡道。如果一切按计划进行,并且你随着新“入口坡道”的出现而保持最新,那么你的代码按照今天的写法将在你升级到新的主要版本时“正常工作”。这有效地使主要版本升级与次要版本升级一样无痛🤯。
此外,通过在 v1 中随着时间推移引入这些功能 —— 我们为应用程序开发人员提供了一个更大的表面积,他们可以在其中分散与 v2 相关的代码更改。
我们理解这是一个崇高的目标,并且我们知道它可能并非总是完全按照我们的计划进行,但是我们对稳定性很认真,并且希望确保我们的流程考虑到主要版本升级可能给我们的应用程序开发人员带来的负担。
我们计划通过我们在 remix.config.js
文件中称为未来标志的方式来实现这一点。将这些视为未来功能的特性标志(现在快速说 5 遍😉)。当我们实现新功能时,我们始终尝试以向后兼容的方式进行。但是,当我们不能并且决定需要进行破坏性更改时,我们不会将该功能推迟到最终 v2 版本。相反,我们添加一个未来标志,并在 v1 次要版本中与当前行为一起实现新功能。这允许用户开始使用该功能、提供反馈并立即报告 bug。
这样,你不仅可以逐步采用功能(并且可以在没有主要版本升级的情况下急切地采用),我们还可以在发布 v2 之前逐步解决任何问题。最终,我们还会向 v1 版本添加弃用警告,以促使用户使用新行为。然后在 v2 中,我们删除旧的 v1 方法、删除弃用并删除标志 —— 从而使标记的行为成为 v2 中的新默认行为。如果在发布 v2 时,一个应用程序已选择加入所有未来标志并更新了他们的代码 —— 那么他们应该只需将其 Remix 依赖项更新到 v2 并从其 remix.config.js
中删除未来标志,并且可以在几分钟内运行 v2。
未来的标志可以有两种形式:future.unstable_feature
或 future.v2_feature
,标志的生命周期将取决于更改的性质以及它是否是破坏性的。引入新功能的决策流程大致如下所示:
因此,生命周期是以下之一:
future.unstable_
标志 -> 进入 v1future.v2_
标志 -> 进入 v2future.unstable_
标志 -> future.v2_
标志 -> 进入 v2为了澄清一下,这里的 unstable_
*并不意味着* 我们认为该功能存在错误!它意味着我们不确定 API 在稳定之前是否会进行一些小的更改。我们*绝对*希望早期采用者开始使用这些功能,以便我们可以在 API 上进行迭代(或获得信心)。
此外,v2_
标志并不意味着该功能没有错误——没有软件是完美的!这意味着我们对 API 有信心,并认为它是 v2 中默认行为的稳定 API。这意味着如果你更新你的代码以在 v1 中使用这个新的 API,你可以使你的 v2 升级*更加*平滑。
以下是今天 Remix v1 中当前标志的列表:
unstable_cssModules
- 启用 CSS 模块支持unstable_cssSideEffectImports
- 启用 CSS 副作用导入unstable_dev
- 启用新的开发服务器(包括 HMR/HDR 支持)unstable_postcss
- 启用 PostCSS 支持unstable_tailwind
- 启用 TailwindCSS 支持unstable_vanillaExtract
- 启用 Vanilla Extract 支持v2_errorBoundary
- 将 ErrorBoundary
/CatchBoundary
合并为一个 ErrorBoundary
v2_meta
- 为你的 meta
函数启用新的 APIv2_routeConvention
- 启用基于文件的路由的扁平路由样式我们正在准备 v2 版本的发布,因此所有 future.unstable_
标志都将被稳定为 future.v2_
标志(除了那些不属于破坏性更改的标志,例如 PostCSS/Tailwind/Vanilla Extract 支持)。这包括为仍在使用旧方法的应用程序添加弃用警告。一旦我们将它们全部稳定下来,我们将发布最终的 Remix 1.15.0 版本,并让它运行一段时间,以便人们有时间选择他们尚未添加的任何标志。然后,我们将计划发布 Remix 2.0.0,并开始致力于发布标志驱动的 Remix v3 功能。
将来,请查看关于此策略的文档,以获取最新的活动未来标志列表。