构建 JavaScript 框架来征服电子商务

2025-05-28

构建 JavaScript 框架来征服电子商务

曾经有一段时间,我从未想过需要写这样一篇文章。即使是在 10 年前,如果你问别人网站是如何运作的,答案也会很简单。一个网站由一系列 HTML 文档组成,这些文档位于不同的位置(URL),每个 URL 描述了页面的显示方式,并提供导航到其他页面的链接。Web 浏览器用于请求和显示这些页面。

但在过去十年里,我们构建 Web 应用的方式发生了翻天覆地的变化。如今,单页应用 (SPA) 正成为一种普遍的产品,传统的多页应用 (MPA) 也需要重新解释。

当我谈论这个问题时,我发现很多 JavaScript 开发者并不理解其中的区别以及它带来的深远影响。像MarkoAstroElderQwik这样的框架与Next.jsNuxt.jsSvelteKit 的架构完全不同

虽然 SPA 带来了很多好处,但今天我要讨论的是它们在哪些方面是不太可优化的解决方案,以及这如何成为一种完全不同的 JavaScript 框架的动力。


2021 年前端 JavaScript 现状

替代文本

绝大多数 JavaScript 框架旨在帮助您创建所谓的单页应用 (SPA)。React 、VueEmberPreactSvelteSolid等等。SPA 简单来说就是一个应用程序,其整个体验都由服务器(或 CDN)发送的单个页面提供。这一特性在基于这些框架构建的 Metaframeworks 中得以延续,例如NextNuxtGatsbySvelteKitRemixBlitz等。

其定义特征是它们基于客户端路由构建。也就是说,浏览器在页面初始加载后处理导航,而无需向服务器发送 HTML 页面请求。然后,JavaScript 会重新渲染部分页面。它们可以选择使用服务器端路由,但应用程序会通过单个入口运行。

这些框架使用起来确实很棒,它们的用例已经从管理仪表板和高度交互的应用程序扩展到博客、内容网站和电子商务等领域。

然而,对于这些SEO和初始页面加载都很重要的网站,我们面临一个问题。我们需要将页面渲染到服务器上,以便页面首次显示时内容能够清晰呈现。


服务器端渲染可以解决问题吗?

替代文本

是也不是。服务器渲染并非免费。没有人愿意因为现在所有东西都放在服务器上而突然维护多个概念性应用程序。一些项目一直在致力于创建一个通用的 JavaScript 环境,让你的单一应用程序代码库能够在服务器和浏览器上无缝运行。

针对不同的部署环境进行配置和托管也可能很复杂。一个简单的解决方案是静态站点生成。我们可以使用该框架的服务端渲染功能提前渲染静态 HTML 页面。

现在,当用户请求页面时,它可以将预先生成的页面发送到浏览器。由于它是静态的,因此可以托管在 CDN 中,并且加载速度非常快。该领域的许多解决方案甚至宣传他们如何实现这种快速的初始渲染,然后由客户端导航接管。

但仍然存在一些问题。首先,静态生成不适用于动态内容。当然,没有什么比预渲染页面更好,但如果页面需要根据用户进行定制,并且涉及不同产品的 A/B 测试等等,那么组合成本很快就会变得非常高昂。在某些情况下,这样做是可以的,解决方案是并行预渲染数万个页面,但对于动态内容来说,如果不付出巨大的成本,就无法保持更新。

即使这不适用于您的网站,更大的问题是,框架和库需要大量的 JavaScript,即使应用程序是服务器渲染的,加载和解析这些 JavaScript 的成本也很高。此外,为了使应用程序在浏览器中具有交互性,JavaScript 框架需要在浏览器中加载或遍历其组件树,以创建初始框架脚手架并连接事件监听器。所有这些都需要时间,并直接影响最终用户体验。

现在我们已经看到一些框架允许你在某些页面关闭 JavaScript,但这基本上是全有或全无的。这虽然可行,但如果我们知道我们正在优化首次渲染和可交互时间,我们可以做得更好。

这确实引出了一个问题:我们能接受这个吗?


多页申请的返回

那么,将应用程序视为独立页面的集合有什么好处呢?页面上的大部分内容根本不需要在浏览器中渲染。

你的页面实际上有多少部分需要重新渲染?答案可能非常少。页面上有多少个点可以供用户交互?如果从图片中移除所有导航,可能并没有你想象的那么多。如果连异步加载也移除会怎么样?

这不一定意味着没有 JavaScript(尽管可以),只是 JavaScript 用量少了点。你可以看到,对于一个像写成一个大程序一样编写的应用程序来说,这很困难。代码拆分在这里并不能真正帮你解决问题。如果页面共享一个自上而下渲染的根节点,我们该如何独立地看待这个问题?我们可以修剪未使用的分支,但不能修剪主干。

很少有框架会针对这种情况进行优化,因为它们并非以此方式构建。当组件树中存在 props 链时,很难将其拆分。实际上,你只有 3 个选择:

  1. 不要。手动将你的页面拆分成一堆微应用或小岛。(Astro
  2. 所有数据都通过依赖注入进行传递。页面的每个部分都是独立的,并根据需要进行传输。( Qwik )
  3. 让编译器足够智能,能够理解应用程序的状态并输出优化的包。(Marko

这些都需要特别考虑。第一个问题需要你识别“孤岛”,并且只有你足够勤奋才能很好地扩展。第二个问题迫使你将状态推到组件之外,这会给 DX 带来很大压力,比如你能传递props.children吗?序列化的内容是否有限制?第三个问题极其复杂,需要专门的语言和多年的研发才能实现。

但效果是显而易见的。以下是Marko团队在 eBay 部分页面上切换此优化后看到的影响的简单示例。

替代文本

此次优化使 JavaScript 包大小节省了 60%-84%!

为什么这么多?Marko本身并不算庞大的库,压缩和 gzip 压缩后只有 13kb。显然,这样可以节省组件代码,但实际成本远不止于此。组件只在服务器上运行也意味着某些 API 封装器和 Moment 和 Lodash 等格式化程序无需访问浏览器。

Marko no-bundle Streaming 在这种情况下也很有用,因为它可以立即提供页面服务,而无需等待异步调用。它可以将内容实时流式传输到服务器渲染的占位符中,而无需将代码拉入 bundle 中。


切中要点

如果您像在电子商务中那样,需要极致的初始加载性能,毫秒之差都意味着潜在的销售损失;您无法保证客户的网络或设备性能;那么​​您就不会选择像Next.js这样的框架。它并没有针对这些情况进行优化。即使您在这里将它与像Preact这样的小型库一起使用,您在浏览器中的操作仍然太多了。

你可能会想,React 18 中新增的服务端组件和流式 SSR等功能怎么样?它们确实能带来一些帮助,但它们本身并不能改变物理原理。

替代文本

正如MarkoSolid所见,流式 SSR 非常强大,因为它消除了异步数据的初始延迟。通过这种方式,您可以消除大部分按需服务器渲染相对于静态站点生成的开销,但仅靠它并不能减少 JavaScript 的发送量。

服务器组件使编写自定义 API 变得更加容易。这省去了将 Lodash 和 Moment 发送到浏览器的步骤,但您仍然需要运行客户端 diff,模板是通过 API 发送的。您可以将其视为某种延迟加载/混合,但处理它实际上会增加核心库的大小。如果您换个角度思考,考虑到服务器组件的规则,这些只是 MPA 永远不会发送到浏览器的静态部分!


结论

合适的工具,等等等等。不过说真的,虽然我梦想着未来这一切和现在一样,但 MPA 框架可以进行一些优化,而这些优化是那些考虑 SPA 架构构建的人无法做到的。

它不需要不同的语言或平台。我不是说要放弃 Rails 或 Django。你仍然可以获得现代的单应用程序 JavaScript 执行,并且感觉已经拥有了可用的工具。但是,如果你关心的是初始页面加载的最佳性能,那么在这些可能的候选者中你不会找到这样的性能。

下次你遇到一个宣传速度的新电商解决方案时,不妨问问它是否针对 MPA 进行了优化,因为如果没有,很可能它就没什么变化了。eBay、阿里巴巴和 Builder 投资构建自己的 JavaScript 框架是有原因的。

这并不是什么新鲜事,而是重新审视 Web 基础。但已经过去十年了,也许是时候了。别误会我的意思。我就是某个 SPA 框架的作者。这个框架以在客户端和服务器端速度最快而自豪。但说到提供最佳用户体验,架构几乎每次都比原始速度更重要。所以,根据你的使用情况,也许你不需要那个 SPA?

文章来源:https://dev.to/this-is-learning/building-javascript-frameworks-to-conquer-ecommerce-3glc
PREV
不使用 JavaScript 的客户端路由
NEXT
后端开发不仅仅是为前端编写端点