Island Architecture
在 SSR 一章中,我们有说到,请求返回的 HTML 中,会有 JS 脚本用于 hydrate,如下是 Vite + Vue 的 SSR 中返回的一个示例页面:
<!DOCTYPE html>
<html lang="en">
<head>
<script type="module" src="/@vite/client"></script>
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"><!--[--><div data-v-7a7a37b1><a href="https://vitejs.dev" target="_blank" data-v-7a7a37b1><img src="/vite.svg" class="logo" alt="Vite logo" data-v-7a7a37b1></a><a href="https://vuejs.org/" target="_blank" data-v-7a7a37b1><img src="/src/assets/vue.svg" class="logo vue" alt="Vue logo" data-v-7a7a37b1></a></div><!-- <HelloWorld msg="Vite + Vue" /> --><!--]--></div>
<!-- 用于 hydrate 的客户端脚本 -->
<script type="module" src="/src/entry-client.ts"></script>
</body>
</html>这个脚本会将 div#app 下面的元素进行注水激活,这使得我们可以先看到页面的内容,减少 FCP,而又不缺失页面的交互性
但上面的场景也存在优化空间,因为这段 JS 的执行也可能会影响到 DOM 的渲染,影响页面的 TTL
实际上,我们并不需要 JS 一次性激活页面全部的交互,如果我们可以选择我们想要激活的部分(组件)进行 hydrate,剩余部分需要的时候再 hydrate,就可以提高页面的性能。
我们把页面拆成多个部分,每个部分被称作 island,也就是岛屿(孤岛),这就是 island 架构
Astro
比较有代表性的是 Astro 框架。对待每个组件都如一个岛屿,我们可以选择激活的时机甚至是激活的框架,这也就是为什么我们能在 Astro 中使用除了官方的 .astro 之外,还可以使用 vue、react、svelte 等多种丰富的框架
举个例子,譬如我们想要我们的组件在视口可见的时候激活,在 Astro 的形式:
// index.astro file
---
import ReactComponent from "./ReactComponent"
---
<ReactComponent client:visible />Qwik
Qwik 也使用了 island 的概念,不过比 Astro 更近一步。不需要手动设置 hydrate 的指令
将 JS 的执行时间还往后推迟了
Delay execution of JavaScript as much as possible.
How qwik works
qwik 工作的基本原理
创建组件
假设我们写了一个 qwik 组件,而且我们的组件看起来像这样:
export const Main = component$(() => {
const state = useState({
message: 'hello',
})
return (
<input
value={state.message}
onInput$={e => state.message = e.target.value}
/>
)
})组件处理
以完全不同的方式加载组件。在服务端,JS 路径被编码在 HTML 中,所以他们不会被浏览器下载,直到它们被需要
<div on:input="./path-to-input-hander.js">
<input value="hello" />
</div>监听和激活
需要用一个轻量的 JS 代码进行激活,以便于在用户动作触发的时候(这个例子是触发 input 事件),下载 JS 代码并执行。代码和下面的类似:
<script>
for (const event of events) {
document.addEventLister(event, e => {
const target = e.target.closet(`on:${event}`)
if (target) import(target.getAttribute(`on:${event}`))
.then(mod => mod.default(e))
})
}
</script>