Islands Architecture

2022-08-04 · 2,661 chars · 14 min read

本文来自 patterns.dev

原文地址:https://www.patterns.dev/posts/islands-architecture/

TL;DR

Islands Architecture 鼓励在 SSR 的页面中使用小的、聚焦于交互的代码块。Islands 的输出是渐进增强的 HTML,并且在增强方式上更加具体。不同于 SPA 的全页面渲染,Islands 有多个入口点。这些可交互 Islands 的 JS 代码可以独立请求并执行 hydrate,同时允许页面的其余部分只是静态 HTML。

加载和执行过多的 JS 是很伤性能的。然而,一定程度上的可交互性和 JS 是必不可少的,甚至是静态网站。我们之前聊过 SSR 的变体,这些变体可以允许你平衡:

  • 与客户端渲染(CSR)相当的交互性
  • 与服务端渲染(SSR)相媲美的 SEO 优势

SSR 的核心原理是:在服务端渲染 HTML,并且附带必要的 JS 来完成 hydrate (客户端的水合)。hydrate 是指服务端渲染完成后,在客户端重建 UI 组件的 state 的过程。由于 hydrate 是有成本的,因此每个 SSR 的变体都在尝试优化这个过程。目前主要是通过关键组件的部分、渐进式 hydrate,或组件在渲染时的流式 SSR 来实现的。然而,最终页面需要加载的的 JS 代码量和上述方案并无不同。

“Islands Architecture”(原文 | 译文)这个术语是 Katie Sylor-Miller 和 Jason Miller 推广开来的,用来描述一种范式:通过可基于静态 HTML 独立交付的交互式 islands,达到降低 JS 体积的目的。Islands 是基于组件的架构,提倡通过静态或动态的 islands 划分页面视图。页面的静态区域是非交互式的纯 HTML,不需要 hydrate。动态区域包含 HTML 和能够在渲染后在客户端自行 hydrate 的 JS。

译者注

图片不翻译了,简单解释下:

  • 左侧:经典的 SSR,所有组件同时 render + hydrate
  • 中间:渐进式 hydrate,服务端 render 所有组件,客户端先 hydrate 关键组件,然后逐渐 hydrate 其他组件
  • 右侧:Islands Architecture,静态部分服务端渲染,仅加载可交互式组件的 JS(并且每部分都是独立的)

接来下,让我们更详细地探索 Islands Architecture,以及目前可用于实现它的不同选择。

动态组件的 Islands#

大多数页面既有动态内容、也有静态内容。通常,一个页面由静态内容和一些可以隔离开的交互区域组成。例如:

  • 博客文章、新闻、主页等。包含带有可交互图片、文字的组件,如内嵌的社交媒体和聊天室
  • 电商类的产品页面。包含静态的产品描述、链接、交互式的组件如图片轮播、搜索。
  • 典型的银行账户的详情页面。带可交互过滤器的静态列表。

静态内容是无状态的 (stateless),不触发事件,无需在客户端进行 hydrate。服务端渲染后,动态的内容(按钮,过滤器,搜索栏)在客户端侧,必须重新注册相关事件,必须重新生成 DOM(Virtual DOM)。这些 “重新生成”,“hydrate”,“事件注册” 依赖于客户端加载的 JS。

Islands Architecture 更适合在那些,在服务端渲染所有静态内容的页面。在这种场景下,渲染出的 HTML 将包括动态内容的占位符。动态内容占位符包含独立的组件小部件。每个小部件都类似于一个应用程序,包含服务器渲染的输出、和用于在客户端 hydrate 应用程序的 JavaScript。

在 progressive hydration(渐进式水合)模式中,页面的 hydrate 是自上而下的,页面控制各个组件的调度和 hydrate。在 Islands Architecture 中,每个组件都有它的 hydrate 脚本,它是异步执行的,与页面上的任何其他脚本无关。一个组件的性能问题不应该影响其他组件。

译者注

图片不翻译了,简单解释下:

  • Page Layout、Blog Title、Blog Content 这三部分,无论是文字还是图片,都是静态的,服务端渲染完,客户端展示即可,不需要 JS 执行
  • 下面的两个组件:Comment 和 SocialButton,都是服务端渲染占位符、HTML、JS,然后在客户端执行 hydrate,执行完成后才可以交互。

实现 Islands#

Islands Architecture 借鉴了很多概念,目的在于将他们更好的组织起来。基于模板的静态网站生成工具,如 Jekyll 和 Hugo,支持渲染静态组件。大部分现代前端 JS 框架都支持同构渲染(isomorphic render),允许你在服务端和客户端使用同一套代码。

Jason 的文章建议使用 requestIdleCallback() 来调度组件 hydrate。同构渲染、组件级的部分 hydrate 等功能,都可以构建在框架中,以支持 Islands Architecture。所以,框架应该:

  • 支持页面的服务端渲染,无需额外的 JavaScript 代码
  • 支持在静态内容中,通过占位符(placeholders)插入独立的动态组件
  • 支持组件的同构渲染

你可以使用下面这几个开箱即用的框架

框架#

现在有很多不同的框架支持 Islands Architecture,其中值得关注的是:

Marko#

Marko 是一个开源框架,由 eBay 开发维护,用于提高服务端渲染的性能。它通过流式渲染(streaming rendering)与自动部分 hydrate 相结合,来支持 Islands architecture。HTML 和其他静态资源,一旦准备好就会立刻流式传输到客户端。交互式组件可以独立、自行完成 hydrate。hydrate 代码仅用于交互式组件,它可以更改浏览器上的 state。它是同构的,Marko 编译器会根据运行位置(客户端、服务器)生成优化后的代码。

Astro#

Astro 是一个静态站点生成工具,可以使用其他框架(如 React、Preact、Svelte、Vue 等)构建的 UI 组件,生成轻量级的静态 HTML 页面。需要客户端 JavaScript 代码的组件会单独加载它们的依赖项。因此,它提供了内置的部分 hydrate。Astro 还可以延迟加载组件。我们下一章节有一个 Astro 的例子。

Eleventy + Preact#

Markus Oberlehner 演示了 Eleventy 的用法,它是一个静态站点生成工具,具有可以部分 hydrate 的同构 Preact 组件。它还支持延迟 hydrate。组件本身以声明方式控制组件的 hydrate。交互式组件使用 WithHydration 修饰器,以便它们在客户端上执行 hydrate。

注意,Marko 和 Eleventy 要早于 Jason 提出 Islands 定义的时间,但包含支持 Islands Architecture 所需的一些功能。Astro 是基于定义构建的,天然支持 Islands 架构。在下一节中,我们用一个简单的博客页面,演示如何使用 Astro。

示例#

以下是我们使用 Astro 实现的博客页面。SamplePost 页面导入了一个交互式组件,SocialButtons。该组件通过标记确定在 HTML 中的具体位置。

Astro page (SamplePost.astro)

---
// Component Imports
import { SocialButtons } from '../../components/SocialButtons.tsx';
---

<html lang="en">
  <head>
    <link rel="stylesheet" href="/blog.css" />
  </head>

  <body>
    <div class="layout">
      <article class="content">
        <section class="intro">
          <h1 class="title">Post title (static)</h1>
          <br />
          <p>Post sub-title (static)</p>
        </section>
        <section class="intro">
          <p>This is the post content with images that is rendered by the server.</p>
          <img src="https://source.unsplash.com/user/c_v_r/200x200" />
          <p>
            The next section contains the interactive social buttons component which
            includes its script.
          </p>
        </section>
        <section class="social">
          <div>
            <SocialButtons client:visible></SocialButtons>
          </div>
        </section>
      </article>
    </div>
  </body>
</html>

SocialButtons 是一个 Preact 组件,包含 HTML 和相应的事件处理逻辑。

SocialButtons component (SocialButtons.tsx)

import { useState } from 'preact/hooks'

/** a counter written in Preact */
export function SocialButtons() {
  const [count, setCount] = useState(0)
  const add = () => setCount((i) => i + 1)
  const subtract = () => setCount((i) => i - 1)

  return (
    <>
      <div>{count} people liked this post</div>
      <div align="right">
        <img src="/like.png" width="32" height="32" onclick={add}></img>
        <img src="/unlike.png" width="32" height="32" onclick={subtract}></img>
      </div>
    </>
  )
}

该组件在运行时嵌入到页面中,并在客户端进行 hydrate,以支持 click 事件。

Astro 允许在 HTML、CSS 和 JS 之间进行清晰的隔离,并鼓励基于组件的设计。使用此框架可以轻松构建网站。

优点和缺点#

Islands Architecture 综合了不同渲染技术的 Idea,例如服务端渲染、静态站点生成和部分 hydrate。Islands 的一些潜在好处是:

  1. 性能:降低客户端 JS 代码的体积。加载的代码,仅包含交互式组件所需的 JS,这比为整个页面重新创建 Virtual DOM,并重新 hydrate 页面上的所有元素所需的 JS 要少得多。较小的 JS 会带来更快的页面加载和交互时间 (TTI)。

Astro 与使用 Next.js 和 Nuxt.js 创建的文档网站相比,JS 代码减少了 83%。其他用户也反馈了使用 Astro 的性能改进。

(图片来自 https://divriots.com/blog/our-experience-with-astro/)

  1. SEO:由于所有静态内容经过服务端渲染,所以页面对 SEO 场景是友好的
  2. 关键内容优先:用户几乎可以立即获得关键内容(尤其是博客、新闻文章和产品类的页面)。在关键内容逐渐可用后,可交互的次要功能也是必须的。
  3. 可访问性:使用标准静态 HTML 链接导航,有助于提高网站的可访问性(Accessibility)。
  4. 基于组件:该架构具有基于组件的架构的所有优点,如:可重用、可维护。

尽管有这些优势,但该概念仍处于初期阶段。“有限的支持”导致了一些缺点:

  1. 开发人员实现 Islands 的唯一选择,是使用少数可用的框架之一,或自己实现架构。将现有站点迁移到 Astro 或 Marko 需要额外的工作量。
  2. 除了 Jason 最初的文章外,几乎没有更多的信息。
  3. 有一些新框架声称支持 Islands Architecture,因此筛选出适合你的架构会更难。
  4. 该架构不适合复杂交互页面,例如可能需要数千个 Islands 的社交应用。

Islands Architecture 的概念相对较新,但由于其性能优势,可能会发展很快。它强调了使用 SSR 渲染静态内容,同时通过动态组件来支持交互性,并且对页面性能的影响最小。希望将来在这个领域看到更多的参与者,并有更广泛的框架可供选择。

进一步阅读#

赞赏

微信