openElement 的 Ocean/Island 模型:服务端通过 DSD 预渲染内容,客户端按需升级交互组件。
"海洋"是纯静态内容(布局、文本、导航),"岛屿"是需要客户端交互的组件。 这种架构的核心思想:大部分页面不需要 JavaScript,只有少数交互点需要。
Declarative Shadow DOM 让服务端渲染的 Web Components 在 HTML 解析阶段就有 shadow root。 用户看到内容的那一刻,不需要任何 JavaScript。
<my-card>
<template shadowrootmode="open">
<style>:host { display: block; }</style>
<p>内容在 JavaScript 加载前即可见。</p>
</template>
</my-card>| 层级 | 类型 | 客户端 JS | 适用场景 |
|---|---|---|---|
| Layer 1 | DSD Static | 无 | 布局、导航、文章内容 |
| Layer 2 | DSD Interactive | 仅事件绑定 | 主题切换、折叠面板、tabs |
| Layer 3 | Pure Island | 完整客户端逻辑 | 图表、复杂表单、WebSocket |
通过 defineIsland() API 声明 island,支持四种 hydration 策略:
import { defineIsland } from '@openelement/core';
export class MyChart extends DsdElement { /* ... */ }
// 立即加载(适合首屏交互元素)
defineIsland(MyChart, { strategy: 'load' });
// 浏览器空闲时加载(适合非关键 UI)
defineIsland(MyChart, { strategy: 'idle' });
// 进入视口时加载(适合懒加载内容)
defineIsland(MyChart, { strategy: 'visible' });
// 纯客户端渲染(无 DSD,无 SSR)
defineIsland(MyChart, { strategy: 'only' });| 策略 | 触发条件 | 推荐用途 |
|---|---|---|
load | 模块加载时 | 首屏交互:导航菜单、搜索框 |
idle | requestIdleCallback | 非关键 UI:页脚组件 |
visible | IntersectionObserver | 懒加载:图片画廊、评论区 |
only | 纯客户端 | 浏览器专用:图表库、地图 |
将需要客户端行为的组件放在 app/islands/ 目录:
// app/islands/counter.ts
import { DsdElement, signal } from '@openelement/core';
export class Counter extends DsdElement {
#count = signal(0);
override render() {
return (
<div>
<button onClick={() => this.#count.value--}>-</button>
<span>{this.#count}</span>
<button onClick={() => this.#count.value++}>+</button>
</div>
);
}
}
customElements.define('my-counter', Counter);在页面中使用:
<my-counter></my-counter>构建器会自动扫描 app/islands/,生成 client entry,并注入到静态 HTML 中。 页面 HTML 先渲染,浏览器加载 island entry 后再升级组件。
| 方面 | SSR(服务端渲染) | CSR(客户端渲染) |
|---|---|---|
| 渲染输出 | DSD HTML 字符串 | shadow root 中的真实 DOM |
| Signal 订阅 | 渲染期间收集、序列化 | 活跃状态,变化时更新 DOM |
| 事件处理 | 序列化等待 hydration | 通过 addEventListener 直接绑定 |
| effect() | 执行一次,捕获输出 | 持续执行 |
| ref | 静默跳过 | 回调被调用 |
customElements.define() 升级已有元素为真正的 Custom Element。