@ubean/islands 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +294 -0
- package/README.zh-CN.md +296 -0
- package/dist/runtime.d.ts +2 -1
- package/package.json +6 -6
package/README.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# @ubean/islands
|
|
2
|
+
|
|
3
|
+
**English | [中文](./README.zh-CN.md)**
|
|
4
|
+
|
|
5
|
+
Islands architecture for ubean — partial hydration for Vue components, plus the server-component protocol.
|
|
6
|
+
|
|
7
|
+
`@ubean/islands` owns everything about **partial hydration** in ubean: the `v-client.*` directive family, the build-time transform that turns directives into `<ubean-island>` elements, the auto-generated registry virtual module, the client hydration runtime, the `.server.vue` / `.client.vue` file conventions, and the `POST /__server-component` endpoint for re-rendering server islands.
|
|
8
|
+
|
|
9
|
+
```vue
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import Counter from '~/components/Counter.vue';
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<Counter v-client.load :initial="0" />
|
|
16
|
+
</template>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Table of Contents
|
|
20
|
+
|
|
21
|
+
- [Package Positioning](#package-positioning)
|
|
22
|
+
- [Install](#install)
|
|
23
|
+
- [Quick Start](#quick-start)
|
|
24
|
+
- [The `v-client` Directive](#the-v-client-directive)
|
|
25
|
+
- [`v-client.only` vs `<ClientOnly>`](#v-clientonly-vs-clientonly)
|
|
26
|
+
- [Directive API](#directive-api)
|
|
27
|
+
- [Programmatic Wrappers](#programmatic-wrappers)
|
|
28
|
+
- [Server / Client Components](#server--client-components)
|
|
29
|
+
- [Constraints](#constraints)
|
|
30
|
+
- [Islands Registry](#islands-registry)
|
|
31
|
+
- [Server Component Endpoint](#server-component-endpoint)
|
|
32
|
+
- [Vite Plugin API](#vite-plugin-api)
|
|
33
|
+
- [SSR Helpers](#ssr-helpers)
|
|
34
|
+
- [Type Safety](#type-safety)
|
|
35
|
+
- [Architecture Notes](#architecture-notes)
|
|
36
|
+
|
|
37
|
+
## Package Positioning
|
|
38
|
+
|
|
39
|
+
`@ubean/islands` is the **islands runtime and build-time transform**:
|
|
40
|
+
|
|
41
|
+
| Layer | Responsibility |
|
|
42
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
43
|
+
| `@ubean/islands` (this pkg) | `v-client.*` directive, island/registry transform, hydration runtime, `.server.vue` / `.client.vue`, `POST /__server-component` |
|
|
44
|
+
| `@ubean/vue` | Page routing and the lean kernel; `<ClientOnly>` and the page/cache/transition layer live there — this package only adds island hydration on top |
|
|
45
|
+
| `@ubean/client` | Calls `hydrateIslands()` automatically after mount and after each SPA navigation — the framework's bridge to this runtime |
|
|
46
|
+
| `@ubean/build` | Wires `ubeanIslandsPlugin()` into `ubeanPlugin()`; the app factory mounts `createServerComponentMiddleware()` |
|
|
47
|
+
|
|
48
|
+
**Included:**
|
|
49
|
+
|
|
50
|
+
- The `v-client.load` / `.idle` / `.visible` / `.media` / `.only` directive family and its strategy resolvers
|
|
51
|
+
- `defineIsland` (client island) / `defineServerIsland` (server island) / `defineClientComponent` / `definePairedComponent`
|
|
52
|
+
- The client hydration runtime: `hydrateIslands`, `collectIslands`, `hydrateIsland`, `hasPendingIslands`, `scheduleIslandHydration`
|
|
53
|
+
- The bootstrap script (`getIslandsBootstrapScript`) and its clear script
|
|
54
|
+
- The server-component protocol: `SERVER_COMPONENT_ENDPOINT`, `registerServerComponent`, `getServerComponent`, `createServerComponentMiddleware`
|
|
55
|
+
- The Vite plugin (`/vite` subpath): directive scan, `<ubean-island>` transform, registry virtual module, `.server.vue` / `.client.vue` resolution and the restricted-parent check
|
|
56
|
+
- SSR helpers for rendering placeholders (`createIslandsContext`, `registerIsland`, `getIslandsScript`, `generateIslandPlaceholder`, `renderIslandPlaceholder`, `hydrationStrategyMeta`)
|
|
57
|
+
|
|
58
|
+
**NOT included:**
|
|
59
|
+
|
|
60
|
+
- Page routing, `<ClientOnly>`, page cache, page transitions, `definePage` — `@ubean/vue`
|
|
61
|
+
- App factories, `defineApp`, the automatic post-mount hydration call and the data layer — `@ubean/client`
|
|
62
|
+
- Full SSR rendering (streaming, head merging, deferred payloads) — `@ubean/client/ssr` and `@ubean/pages`
|
|
63
|
+
- Server routing and the Hono app — `@ubean/app`
|
|
64
|
+
|
|
65
|
+
**Runtime dependency whitelist (main entry): `vue`.** The main entry imports only `./directive` and `./runtime` — no `node:*`, no `vite`. `hono` and `pathe` are used by the server-component middleware and the Vite plugin, which live behind the `/server` and `/vite` subpaths. `vite` is an **optional peer** used only by the build-time subpath.
|
|
66
|
+
|
|
67
|
+
## Install
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pnpm add ubean # or: pnpm add @ubean/islands
|
|
71
|
+
# build-time (the /vite subpath):
|
|
72
|
+
pnpm add -D vite
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Quick Start
|
|
76
|
+
|
|
77
|
+
**1. Vite config** — the plugin scans `.vue` files, transforms `v-client.*` tags and generates the registry:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// vite.config.ts
|
|
81
|
+
import vue from '@vitejs/plugin-vue';
|
|
82
|
+
import { ubeanIslandsPlugin } from '@ubean/islands/vite';
|
|
83
|
+
|
|
84
|
+
export default {
|
|
85
|
+
plugins: [vue(), ubeanIslandsPlugin()]
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**2. Mark the interactive component** — put the directive on the **component element**, not a plain `<div>`:
|
|
90
|
+
|
|
91
|
+
```vue
|
|
92
|
+
<script setup lang="ts">
|
|
93
|
+
import Counter from '~/components/Counter.vue';
|
|
94
|
+
import HeavyChart from '~/components/HeavyChart.vue';
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<template>
|
|
98
|
+
<Counter v-client.load />
|
|
99
|
+
<HeavyChart v-client.idle />
|
|
100
|
+
</template>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**3. Nothing else.** Import the component in `<script setup>` and the framework hydrates it after mount — no code in `app.ts` is required.
|
|
104
|
+
|
|
105
|
+
A complete example lives in [`apps/docs/src/content/en/guide/islands.md`](../../apps/docs/src/content/en/guide/islands.md).
|
|
106
|
+
|
|
107
|
+
## The `v-client` Directive
|
|
108
|
+
|
|
109
|
+
The modifier selects the hydration strategy — one directive per strategy, all declared on the component tag:
|
|
110
|
+
|
|
111
|
+
| Directive | Strategy | Hydrates when |
|
|
112
|
+
| ------------------ | ----------- | -------------------------------------------------- |
|
|
113
|
+
| `v-client.load` | `'load'` | Immediately on page load |
|
|
114
|
+
| `v-client.idle` | `'idle'` | Browser is idle (`requestIdleCallback`) |
|
|
115
|
+
| `v-client.visible` | `'visible'` | Element scrolls into view (`IntersectionObserver`) |
|
|
116
|
+
| `v-client.media` | `'media'` | A CSS media query matches (value is the query) |
|
|
117
|
+
| `v-client.only` | `'only'` | Client only — no SSR output for this component |
|
|
118
|
+
|
|
119
|
+
```vue
|
|
120
|
+
<template>
|
|
121
|
+
<Counter v-client.load />
|
|
122
|
+
<HeavyChart v-client.idle />
|
|
123
|
+
<Comments v-client.visible />
|
|
124
|
+
<MobileNav v-client.media="'(max-width: 768px)'" />
|
|
125
|
+
<ClientOnlyWidget v-client.only />
|
|
126
|
+
</template>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The value of `.media` is a Vue expression, so a string literal must be quoted. Props passed to an island must be JSON-serializable (strings, numbers, booleans, arrays, plain objects).
|
|
130
|
+
|
|
131
|
+
### `v-client.only` vs `<ClientOnly>`
|
|
132
|
+
|
|
133
|
+
`<ClientOnly>` (from `@ubean/vue` / `@ubean/client`) covers the non-island case and takes a `fallback` prop / `#fallback` slot:
|
|
134
|
+
|
|
135
|
+
| Use `v-client.only` | Use `<ClientOnly>` |
|
|
136
|
+
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
137
|
+
| Component-level islands (registry-driven hydration) | Template fragments / plain HTML (the islands transform only matches capitalized component tags) |
|
|
138
|
+
| Needs a deferral strategy alongside (load/idle/visible/media) | Same-tree rendering with full app context |
|
|
139
|
+
| Content is replaced by a separate app on hydration | Must not sit inside an island placeholder (it is wiped when the island hydrates) |
|
|
140
|
+
|
|
141
|
+
## Directive API
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import {
|
|
145
|
+
vClient,
|
|
146
|
+
resolveClientStrategy,
|
|
147
|
+
strategyToLegacyDirective,
|
|
148
|
+
legacyDirectiveToStrategy
|
|
149
|
+
} from '@ubean/islands/directive';
|
|
150
|
+
|
|
151
|
+
vClient; // the Vue directive object (ObjectDirective<HTMLElement, string, ClientStrategy>)
|
|
152
|
+
resolveClientStrategy({ load: true, media: true }); // → 'load' | 'idle' | 'visible' | 'media' | 'only'
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- `ClientStrategy = 'load' | 'idle' | 'visible' | 'media' | 'only'`; `ClientDirective = 'client:load' | 'client:idle' | 'client:visible' | 'client:media' | 'client:only'`.
|
|
156
|
+
- `applyStrategy(el, strategy, mediaQuery?)` / `cleanupStrategy(el)` are the imperative primitives the directive uses.
|
|
157
|
+
- The `data-*` protocol attributes are exported for tooling: `CLIENT_DIRECTIVE_ATTR` (`data-client-directive`), `CLIENT_MEDIA_ATTR` (`data-media`), `CLIENT_ONLY_ATTR` (`data-client-only`).
|
|
158
|
+
- The `/directive` subpath is browser-safe and has no build-time dependency — it is what `@ubean/client` imports.
|
|
159
|
+
|
|
160
|
+
## Programmatic Wrappers
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { defineIsland, defineServerIsland } from '@ubean/islands';
|
|
164
|
+
import AsyncChart from '~/components/AsyncChart.vue';
|
|
165
|
+
import Counter from '~/components/Counter.vue';
|
|
166
|
+
|
|
167
|
+
// Client island; strategy: 'load' | 'idle' | 'visible' | 'media' | 'only'
|
|
168
|
+
const CounterIsland = defineIsland(Counter, 'load');
|
|
169
|
+
|
|
170
|
+
// 'media' requires a mediaQuery; props can be pre-bound
|
|
171
|
+
const MobileNavIsland = defineIsland(MobileNav, 'media', {
|
|
172
|
+
mediaQuery: '(max-width: 768px)',
|
|
173
|
+
props: { theme: 'dark' }
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Server island — wraps the async component in <Suspense> with a fallback
|
|
177
|
+
const Chart = defineServerIsland(AsyncChart, { fallback: '<div class="skeleton">Loading…</div>' });
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
- `IslandOptions = { mediaQuery?: string, props?: Record<string, unknown> }`.
|
|
181
|
+
- `ServerIslandOptions = { fallback?: Component | string, rerenderOnPropsChange?: boolean }`. When omitted, a `<ubean-defer-fallback>` placeholder is used.
|
|
182
|
+
- With `rerenderOnPropsChange: true`, the client requests the server-rendered HTML once after mount and again whenever props change, replacing the container's content; the SSR side registers the component so the endpoint can find it. The Vite plugin injects the component's absolute path as the third argument — without it the option degrades to `false`.
|
|
183
|
+
- `defineClientComponent(component)` and `definePairedComponent(ServerComp, ClientComp)` are the runtime halves of the `.client.vue` and `.server.vue` + `.client.vue` conventions; both are normally generated by the plugin, not written by hand.
|
|
184
|
+
|
|
185
|
+
## Server / Client Components
|
|
186
|
+
|
|
187
|
+
Two file-name conventions for components that belong to exactly one build graph. The plugin handles resolution — no import change and no directive needed.
|
|
188
|
+
|
|
189
|
+
- **`Foo.client.vue`** — renders only on the client. SSR emits a `<!--client-only-->` comment placeholder and the real component replaces it after mount. For components that need browser APIs.
|
|
190
|
+
- **`Foo.server.vue`** — renders only on the server. The client build swaps the import for a stub, so neither the implementation nor its imports reach the client bundle. For server-only access (env vars, databases).
|
|
191
|
+
- **`Foo.server.vue` + `Foo.client.vue` together** — import the base name (`import Foo from './Foo.vue'`); that file must **not** exist. SSR renders the server half, the client half takes over after mount.
|
|
192
|
+
|
|
193
|
+
### Constraints
|
|
194
|
+
|
|
195
|
+
**A `.server.vue` must be a `<template>`-form SFC.** The server wraps the template in `<ubean-server-only v-once>` so hydration can match it; a render-function SFC (or `export { default } from …`) has nothing to wrap and the build fails with that reason.
|
|
196
|
+
|
|
197
|
+
**A `.server.vue` occupies a wrapper element, so its context matters.** The client keeps a stub in the tree and hydration preserves the server-rendered children by matching that element. The wrapper is an ordinary flow element, so a server component may only be used where a flow element is allowed:
|
|
198
|
+
|
|
199
|
+
```html
|
|
200
|
+
<!-- breaks: the wrapper is not valid inside <tr>, so the parser hoists it out of the table -->
|
|
201
|
+
<table>
|
|
202
|
+
<tr>
|
|
203
|
+
<ServerGreeting />
|
|
204
|
+
</tr>
|
|
205
|
+
</table>
|
|
206
|
+
|
|
207
|
+
<!-- fine: the server component renders the container itself -->
|
|
208
|
+
<ServerTable />
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**The plugin rejects this at transform time** (dev overlay / `vite build` error), naming the parent and the component. The failure is deliberate: the symptom it prevents is silent in production — content appears _outside_ the container before hydration, and afterwards Vue reports `Hydration node mismatch` / `Hydration children mismatch` and removes the content.
|
|
212
|
+
|
|
213
|
+
The check covers table ancestors (`table`, `thead`, `tbody`, `tfoot`, `tr`, `colgroup`) and `select` / `optgroup`. It deliberately does **not** cover `ul` / `ol` / `dl`: there the wrapper stays put, so both sides produce the same DOM and hydration is fine.
|
|
214
|
+
|
|
215
|
+
Workarounds: let the server component render the container (the whole `<table>` / `<ul>`), or place it inside an allowed child (`<td>`, `<li>`). `.client.vue` has no such constraint — a comment node is legal in every context.
|
|
216
|
+
|
|
217
|
+
## Islands Registry
|
|
218
|
+
|
|
219
|
+
`virtual:ubean-islands-registry` is generated by the plugin from the `<script setup>` imports of every file that uses a `v-client.*` directive:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
declare module 'virtual:ubean-islands-registry' {
|
|
223
|
+
export const islands: Record<string, Component | (() => Promise<Component>)>;
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
- The key is the **template tag name**; the value is a lazy loader, so island components never land in the entry chunk.
|
|
228
|
+
- The module exports `{}` when nothing was scanned.
|
|
229
|
+
- `ISLANDS_REGISTRY_VIRTUAL_ID` (from `/vite`) is the virtual module id; the `/virtual-registration` subpath is **types-only** (`"import": null`) — it exists so editors can resolve the module's type without pulling in the plugin.
|
|
230
|
+
- A component reached only through a global registration, `defineAsyncComponent` or a dynamic import cannot be auto-registered; pass it manually through `hydrateIslands({ components })` (manual entries win).
|
|
231
|
+
- Only components actually used with a `v-client.*` directive are included in the client bundle.
|
|
232
|
+
|
|
233
|
+
The pipeline, end to end: **scan** `.vue` files → **transform** island tags into `<ubean-island v-once>` (with serialized props in data attributes) → **resolve** import paths → **generate** the registry virtual module → **bridge** it through `hydrateIslands` in `@ubean/client` → **auto-hydrate** after mount (double `requestAnimationFrame`) and after each SPA navigation.
|
|
234
|
+
|
|
235
|
+
## Server Component Endpoint
|
|
236
|
+
|
|
237
|
+
`createServerComponentMiddleware()` handles `POST /__server-component` — the endpoint the client island wrapper posts to when it needs fresh server HTML:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { SERVER_COMPONENT_ENDPOINT, createServerComponentMiddleware } from '@ubean/islands/server';
|
|
241
|
+
|
|
242
|
+
SERVER_COMPONENT_ENDPOINT; // '/__server-component'
|
|
243
|
+
app.use('*', createServerComponentMiddleware());
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
- Request body: `{ "path": "/abs/path/Foo.server.vue", "props": { … } }`.
|
|
247
|
+
- Responses: `200` HTML / `400` missing path / `404` unregistered component / `500` render failure; every response carries `Cache-Control: no-store`.
|
|
248
|
+
- `registerServerComponent(path, component)` / `getServerComponent(path)` are the registry accessors the SSR renderer and the middleware share.
|
|
249
|
+
- The registry deliberately lives on **`globalThis`**: in dev the SSR graph inlines this package's runtime while `@ubean/app` resolves `@ubean/islands/server` through Node, so a module-level `Map` would give the two instances different registries — the middleware would 404 for components that were registered (guard: `packages/islands/test/server-component-registry-singleton.test.ts`).
|
|
250
|
+
- `isServerComponentRequest(c)` / `isServerComponentResponse(response)` and `SERVER_COMPONENT_RESPONSE_HEADER` (`x-ubean-server-component`) let a request-logging or proxy layer identify these calls.
|
|
251
|
+
|
|
252
|
+
## Vite Plugin API
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { ubeanIslandsPlugin } from '@ubean/islands/vite';
|
|
256
|
+
|
|
257
|
+
export default {
|
|
258
|
+
plugins: [ubeanIslandsPlugin({ enabled: true })]
|
|
259
|
+
};
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`UbeanIslandsPluginOptions = { enabled?: boolean }`. The subpath also exposes the transform's building blocks for advanced integrations: `isServerComponentFile`, `isClientComponentFile`, `hasServerComponentTemplate`, `wrapServerComponentTemplate`, `findRestrictedServerComponentUsage`, `injectServerComponentPath`, `transformVueSfcIslands`, `collectIslandComponents`, `generateRegistryModule`, `parseScriptImports`, `resolveIslandImportPath`, `scanIslandDirectiveNames`, plus the virtual ids `ISLANDS_REGISTRY_VIRTUAL_ID`, `SERVER_COMPONENT_STUB_VIRTUAL_ID` and `CLIENT_COMPONENT_PLACEHOLDER_VIRTUAL_ID`.
|
|
263
|
+
|
|
264
|
+
The plugin is intentionally **not** re-exported from the main entry — it pulls `node:fs` / `node:path`, which must not reach a browser bundle.
|
|
265
|
+
|
|
266
|
+
## SSR Helpers
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import { createIslandsContext, registerIsland, getIslandsScript, renderIslandPlaceholder } from '@ubean/islands';
|
|
270
|
+
|
|
271
|
+
const ctx = createIslandsContext();
|
|
272
|
+
const id = registerIsland(ctx, 'Counter', 'client:load', { initial: 10 });
|
|
273
|
+
const html = renderIslandPlaceholder({ component: 'Counter', directive: 'client:load', props: { initial: 10 } });
|
|
274
|
+
const script = getIslandsScript([...ctx.islands.values()]);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
- `getIslandsBootstrapScript()` emits the IIFE that marks islands for hydration according to their strategy; `getIslandsClearScript()` its counterpart.
|
|
278
|
+
- `hydrationStrategyMeta` maps each directive to its strategy metadata.
|
|
279
|
+
- These are for custom SSR pipelines; the framework's own renderer already calls them.
|
|
280
|
+
|
|
281
|
+
## Type Safety
|
|
282
|
+
|
|
283
|
+
- `ClientStrategy` / `ClientDirective` / `ClientDirectiveBinding` / `VClientDirective` type the directive; `IslandStrategy` / `IslandOptions` / `ServerIslandOptions` the wrappers.
|
|
284
|
+
- `IslandRecord` / `IslandHydrateOptions` / `HydrateIslandsOptions` / `IslandHydrationScheduler` type the hydration runtime; `IslandsContext` / `IslandDefinition` / `IslandSsrOptions` the SSR helpers.
|
|
285
|
+
- `declare module 'virtual:ubean-islands-registry'` ships with the package, so the virtual module is typed without extra setup.
|
|
286
|
+
|
|
287
|
+
## Architecture Notes
|
|
288
|
+
|
|
289
|
+
- **Two layers, one job:** the `v-client.*` directive is the authoring surface; the registry plus the hydration runtime is the mechanism. You never call `hydrateIslands()` by hand in a normal app.
|
|
290
|
+
- **Browser-safe main entry:** `dist/index.js` imports only `./directive` and `./runtime`. The Vite plugin (`/vite`) and the Hono middleware (`/server`) are separate subpaths.
|
|
291
|
+
- **Registry on `globalThis`:** required because dev loads two module instances (see above); a module-scoped `Map` would silently split the registry.
|
|
292
|
+
- **The transform is deliberate about HTML parsing:** only parents where the parser actually reparents or drops the wrapper are rejected; `ul` / `ol` / `dl` are left alone to avoid false positives.
|
|
293
|
+
- **Progressive enhancement of the transform:** the plugin errors at build time rather than letting hydration remove content silently in production.
|
|
294
|
+
- **Ownership:** `@ubean/build` wires the plugin; `@ubean/client` owns the automatic hydration call; `@ubean/islands` owns the mechanism.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# @ubean/islands
|
|
2
|
+
|
|
3
|
+
**[English](./README.md) | 中文**
|
|
4
|
+
|
|
5
|
+
ubean 的 Islands 架构 —— Vue 组件的局部水合,以及服务端组件协议。
|
|
6
|
+
|
|
7
|
+
`@ubean/islands` 拥有 ubean 中**局部水合**的全部内容:`v-client.*` 指令族、把指令转换为 `<ubean-island>` 元素的构建期转换、自动生成的注册表虚拟模块、客户端水合运行时、`.server.vue` / `.client.vue` 文件约定,以及用于重渲染服务端岛屿的 `POST /__server-component` 端点。
|
|
8
|
+
|
|
9
|
+
```vue
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import Counter from '~/components/Counter.vue';
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<Counter v-client.load :initial="0" />
|
|
16
|
+
</template>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 目录
|
|
20
|
+
|
|
21
|
+
- [@ubean/islands](#ubeanislands)
|
|
22
|
+
- [目录](#目录)
|
|
23
|
+
- [包定位](#包定位)
|
|
24
|
+
- [安装](#安装)
|
|
25
|
+
- [快速开始](#快速开始)
|
|
26
|
+
- [`v-client` 指令](#v-client-指令)
|
|
27
|
+
- [`v-client.only` 与 `<ClientOnly>`](#v-clientonly-与-clientonly)
|
|
28
|
+
- [指令 API](#指令-api)
|
|
29
|
+
- [编程式包装器](#编程式包装器)
|
|
30
|
+
- [Server / Client 组件](#server--client-组件)
|
|
31
|
+
- [约束](#约束)
|
|
32
|
+
- [Islands 注册表](#islands-注册表)
|
|
33
|
+
- [服务端组件端点](#服务端组件端点)
|
|
34
|
+
- [Vite 插件 API](#vite-插件-api)
|
|
35
|
+
- [SSR 助手](#ssr-助手)
|
|
36
|
+
- [类型安全](#类型安全)
|
|
37
|
+
- [架构说明](#架构说明)
|
|
38
|
+
|
|
39
|
+
## 包定位
|
|
40
|
+
|
|
41
|
+
`@ubean/islands` 是**岛屿运行时 + 构建期转换**:
|
|
42
|
+
|
|
43
|
+
| 分层 | 职责 |
|
|
44
|
+
| ------------------------ | --------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| `@ubean/islands`(本包) | `v-client.*` 指令、岛屿/注册表转换、水合运行时、`.server.vue` / `.client.vue`、`POST /__server-component` |
|
|
46
|
+
| `@ubean/vue` | 页面路由与精简内核;`<ClientOnly>` 与页面/缓存/过渡层在那里 —— 本包只是在其上叠加岛屿水合 |
|
|
47
|
+
| `@ubean/client` | mount 之后与每次 SPA 导航后自动调用 `hydrateIslands()` —— 框架通往本运行时的桥 |
|
|
48
|
+
| `@ubean/build` | 把 `ubeanIslandsPlugin()` 接进 `ubeanPlugin()`;应用工厂挂载 `createServerComponentMiddleware()` |
|
|
49
|
+
|
|
50
|
+
**包含:**
|
|
51
|
+
|
|
52
|
+
- `v-client.load` / `.idle` / `.visible` / `.media` / `.only` 指令族及其策略解析器
|
|
53
|
+
- `defineIsland`(客户端岛屿)/ `defineServerIsland`(服务端岛屿)/ `defineClientComponent` / `definePairedComponent`
|
|
54
|
+
- 客户端水合运行时:`hydrateIslands`、`collectIslands`、`hydrateIsland`、`hasPendingIslands`、`scheduleIslandHydration`
|
|
55
|
+
- 引导脚本(`getIslandsBootstrapScript`)及其清理脚本
|
|
56
|
+
- 服务端组件协议:`SERVER_COMPONENT_ENDPOINT`、`registerServerComponent`、`getServerComponent`、`createServerComponentMiddleware`
|
|
57
|
+
- Vite 插件(`/vite` 子路径):指令扫描、`<ubean-island>` 转换、注册表虚拟模块、`.server.vue` / `.client.vue` 解析与受限父级检查
|
|
58
|
+
- 用于渲染占位符的 SSR 助手(`createIslandsContext`、`registerIsland`、`getIslandsScript`、`generateIslandPlaceholder`、`renderIslandPlaceholder`、`hydrationStrategyMeta`)
|
|
59
|
+
|
|
60
|
+
**不包含:**
|
|
61
|
+
|
|
62
|
+
- 页面路由、`<ClientOnly>`、页面缓存、页面过渡、`definePage` —— `@ubean/vue`
|
|
63
|
+
- 应用工厂、`defineApp`、mount 后的自动水合调用、数据层 —— `@ubean/client`
|
|
64
|
+
- 完整 SSR 渲染(流式、head 合并、延迟数据 payload)—— `@ubean/client/ssr` 与 `@ubean/pages`
|
|
65
|
+
- 服务端路由与 Hono 应用 —— `@ubean/app`
|
|
66
|
+
|
|
67
|
+
**运行时依赖白名单(主入口):`vue`。** 主入口只导入 `./directive` 与 `./runtime` —— 没有 `node:*`,也没有 `vite`。`hono` 与 `pathe` 由服务端组件中间件与 Vite 插件使用,它们分别位于 `/server` 与 `/vite` 子路径之后。`vite` 是 **optional peer**,仅构建期子路径使用。
|
|
68
|
+
|
|
69
|
+
## 安装
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pnpm add ubean # 或:pnpm add @ubean/islands
|
|
73
|
+
# 构建期(/vite 子路径):
|
|
74
|
+
pnpm add -D vite
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 快速开始
|
|
78
|
+
|
|
79
|
+
**1. Vite 配置** —— 插件扫描 `.vue` 文件、转换 `v-client.*` 标签并生成注册表:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// vite.config.ts
|
|
83
|
+
import vue from '@vitejs/plugin-vue';
|
|
84
|
+
import { ubeanIslandsPlugin } from '@ubean/islands/vite';
|
|
85
|
+
|
|
86
|
+
export default {
|
|
87
|
+
plugins: [vue(), ubeanIslandsPlugin()]
|
|
88
|
+
};
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**2. 标记交互组件** —— 指令要落在**组件元素**上,而不是普通 `<div>`:
|
|
92
|
+
|
|
93
|
+
```vue
|
|
94
|
+
<script setup lang="ts">
|
|
95
|
+
import Counter from '~/components/Counter.vue';
|
|
96
|
+
import HeavyChart from '~/components/HeavyChart.vue';
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<template>
|
|
100
|
+
<Counter v-client.load />
|
|
101
|
+
<HeavyChart v-client.idle />
|
|
102
|
+
</template>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**3. 其余什么都不用做。** 在 `<script setup>` 中导入组件,框架会在 mount 后自动水合 —— `app.ts` 中无需任何代码。
|
|
106
|
+
|
|
107
|
+
完整示例见 [`apps/docs/src/content/en/guide/islands.md`](../../apps/docs/src/content/en/guide/islands.md)。
|
|
108
|
+
|
|
109
|
+
## `v-client` 指令
|
|
110
|
+
|
|
111
|
+
修饰符决定水合策略 —— 每条策略一个指令,都声明在组件标签上:
|
|
112
|
+
|
|
113
|
+
| 指令 | 策略 | 何时水合 |
|
|
114
|
+
| ------------------ | ----------- | ----------------------------------------- |
|
|
115
|
+
| `v-client.load` | `'load'` | 页面加载后立即水合 |
|
|
116
|
+
| `v-client.idle` | `'idle'` | 浏览器空闲时(`requestIdleCallback`) |
|
|
117
|
+
| `v-client.visible` | `'visible'` | 元素进入视口时(`IntersectionObserver`) |
|
|
118
|
+
| `v-client.media` | `'media'` | 某条 CSS media query 命中时(值为该查询) |
|
|
119
|
+
| `v-client.only` | `'only'` | 仅客户端 —— 该组件不产出 SSR HTML |
|
|
120
|
+
|
|
121
|
+
```vue
|
|
122
|
+
<template>
|
|
123
|
+
<Counter v-client.load />
|
|
124
|
+
<HeavyChart v-client.idle />
|
|
125
|
+
<Comments v-client.visible />
|
|
126
|
+
<MobileNav v-client.media="'(max-width: 768px)'" />
|
|
127
|
+
<ClientOnlyWidget v-client.only />
|
|
128
|
+
</template>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`.media` 的值是 Vue 表达式,因此字符串字面量必须加引号。传给岛屿的 props 必须可 JSON 序列化(字符串、数字、布尔、数组、纯对象)。
|
|
132
|
+
|
|
133
|
+
### `v-client.only` 与 `<ClientOnly>`
|
|
134
|
+
|
|
135
|
+
`<ClientOnly>`(来自 `@ubean/vue` / `@ubean/client`)覆盖非岛屿场景,接受 `fallback` prop / `#fallback` slot:
|
|
136
|
+
|
|
137
|
+
| 用 `v-client.only` | 用 `<ClientOnly>` |
|
|
138
|
+
| ----------------------------------------------- | ---------------------------------------------------------- |
|
|
139
|
+
| 组件级岛屿(由注册表驱动水合) | 模板片段 / 普通 HTML(岛屿转换只匹配首字母大写的组件标签) |
|
|
140
|
+
| 需要同时指定延后策略(load/idle/visible/media) | 同树渲染且拥有完整应用上下文 |
|
|
141
|
+
| 组件内容由独立应用在 hydration 时替换 | 不能放进岛屿占位符内部(岛屿水合时会被清除) |
|
|
142
|
+
|
|
143
|
+
## 指令 API
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import {
|
|
147
|
+
vClient,
|
|
148
|
+
resolveClientStrategy,
|
|
149
|
+
strategyToLegacyDirective,
|
|
150
|
+
legacyDirectiveToStrategy
|
|
151
|
+
} from '@ubean/islands/directive';
|
|
152
|
+
|
|
153
|
+
vClient; // Vue 指令对象(ObjectDirective<HTMLElement, string, ClientStrategy>)
|
|
154
|
+
resolveClientStrategy({ load: true, media: true }); // → 'load' | 'idle' | 'visible' | 'media' | 'only'
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
- `ClientStrategy = 'load' | 'idle' | 'visible' | 'media' | 'only'`;`ClientDirective = 'client:load' | 'client:idle' | 'client:visible' | 'client:media' | 'client:only'`。
|
|
158
|
+
- `applyStrategy(el, strategy, mediaQuery?)` / `cleanupStrategy(el)` 是指令底层的命令式原语。
|
|
159
|
+
- 供工具链使用的 `data-*` 协议属性已导出:`CLIENT_DIRECTIVE_ATTR`(`data-client-directive`)、`CLIENT_MEDIA_ATTR`(`data-media`)、`CLIENT_ONLY_ATTR`(`data-client-only`)。
|
|
160
|
+
- `/directive` 子路径浏览器安全且无构建期依赖 —— `@ubean/client` 导入的正是它。
|
|
161
|
+
|
|
162
|
+
## 编程式包装器
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { defineIsland, defineServerIsland } from '@ubean/islands';
|
|
166
|
+
import AsyncChart from '~/components/AsyncChart.vue';
|
|
167
|
+
import Counter from '~/components/Counter.vue';
|
|
168
|
+
|
|
169
|
+
// 客户端岛屿;strategy: 'load' | 'idle' | 'visible' | 'media' | 'only'
|
|
170
|
+
const CounterIsland = defineIsland(Counter, 'load');
|
|
171
|
+
|
|
172
|
+
// 'media' 必须提供 mediaQuery;props 可预先绑定
|
|
173
|
+
const MobileNavIsland = defineIsland(MobileNav, 'media', {
|
|
174
|
+
mediaQuery: '(max-width: 768px)',
|
|
175
|
+
props: { theme: 'dark' }
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// 服务端岛屿 —— 把异步组件包进带 fallback 的 <Suspense>
|
|
179
|
+
const Chart = defineServerIsland(AsyncChart, { fallback: '<div class="skeleton">Loading…</div>' });
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- `IslandOptions = { mediaQuery?: string, props?: Record<string, unknown> }`。
|
|
183
|
+
- `ServerIslandOptions = { fallback?: Component | string, rerenderOnPropsChange?: boolean }`。省略时使用 `<ubean-defer-fallback>` 占位元素。
|
|
184
|
+
- 开启 `rerenderOnPropsChange: true` 后,客户端在 mount 后请求一次服务端渲染的 HTML,并在 props 变化时重复请求,用返回值替换容器内容;SSR 侧会把组件注册进全局注册表以便端点查找。Vite 插件会注入组件绝对路径作为第三个参数 —— 没有该参数时该选项退化为 `false`。
|
|
185
|
+
- `defineClientComponent(component)` 与 `definePairedComponent(ServerComp, ClientComp)` 分别是 `.client.vue` 与 `.server.vue` + `.client.vue` 约定的运行时半边;两者通常由插件生成,无需手写。
|
|
186
|
+
|
|
187
|
+
## Server / Client 组件
|
|
188
|
+
|
|
189
|
+
两个文件名约定,用于只属于单一构建图的组件。插件负责解析 —— 无需改导入,也无需指令。
|
|
190
|
+
|
|
191
|
+
- **`Foo.client.vue`** —— 只在客户端渲染。SSR 输出 `<!--client-only-->` 注释占位符,真实组件在 mount 后替换它。适用于需要浏览器 API 的组件。
|
|
192
|
+
- **`Foo.server.vue`** —— 只在服务端渲染。客户端构建把该导入换成 stub,因此实现与其依赖都不会进入客户端产物。适用于服务端专属访问(环境变量、数据库)。
|
|
193
|
+
- **`Foo.server.vue` + `Foo.client.vue` 成对存在** —— 导入基础名(`import Foo from './Foo.vue'`);该文件必须**不存在**。SSR 渲染服务端半边,客户端半边在 mount 后接管。
|
|
194
|
+
|
|
195
|
+
### 约束
|
|
196
|
+
|
|
197
|
+
**`.server.vue` 必须是 `<template>` 形式的 SFC。** 服务端会把模板包进 `<ubean-server-only v-once>` 以便水合匹配;render 函数式 SFC(或 `export { default } from …`)没有可包裹的模板,构建会以该原因失败。
|
|
198
|
+
|
|
199
|
+
**`.server.vue` 会占据一个包裹元素,因此上下文很重要。** 客户端在树中保留一个 stub,水合通过匹配该元素来保留服务端渲染的子节点。该包裹元素是普通的流式元素,所以服务端组件只能用在允许流式元素的位置:
|
|
200
|
+
|
|
201
|
+
```html
|
|
202
|
+
<!-- 会出错:包裹元素在 <tr> 中不合法,解析器会把它提到 table 之外 -->
|
|
203
|
+
<table>
|
|
204
|
+
<tr>
|
|
205
|
+
<ServerGreeting />
|
|
206
|
+
</tr>
|
|
207
|
+
</table>
|
|
208
|
+
|
|
209
|
+
<!-- 正确:由服务端组件自己渲染容器 -->
|
|
210
|
+
<ServerTable />
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**插件在转换阶段就会拒绝这种写法**(dev overlay / `vite build` 报错),并指出父元素与组件名。这个失败是刻意的:它要防止的症状在生产环境是**静默**的 —— 水合前内容出现在容器**之外**,之后 Vue 报 `Hydration node mismatch` / `Hydration children mismatch` 并把内容移除。
|
|
214
|
+
|
|
215
|
+
检查覆盖表格祖先(`table`、`thead`、`tbody`、`tfoot`、`tr`、`colgroup`)以及 `select` / `optgroup`。它刻意**不**覆盖 `ul` / `ol` / `dl`:在那里包裹元素不会被移动,两侧 DOM 一致,水合正常。
|
|
216
|
+
|
|
217
|
+
解法:让服务端组件自己渲染容器(整个 `<table>` / `<ul>`),或把它放进允许的子元素(`<td>`、`<li>`)。`.client.vue` 没有此约束 —— 注释节点在任何上下文都合法。
|
|
218
|
+
|
|
219
|
+
## Islands 注册表
|
|
220
|
+
|
|
221
|
+
`virtual:ubean-islands-registry` 由插件从每个使用了 `v-client.*` 指令的文件的 `<script setup>` 导入生成:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
declare module 'virtual:ubean-islands-registry' {
|
|
225
|
+
export const islands: Record<string, Component | (() => Promise<Component>)>;
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
- key 是**模板标签名**;value 是惰性 loader,因此岛屿组件永远不会进入入口 chunk。
|
|
230
|
+
- 未扫描到任何内容时该模块导出 `{}`。
|
|
231
|
+
- `ISLANDS_REGISTRY_VIRTUAL_ID`(来自 `/vite`)是虚拟模块 id;`/virtual-registration` 子路径是**纯类型**(`"import": null`)—— 它的存在只为让编辑器在不引入插件的情况下解析该模块类型。
|
|
232
|
+
- 仅通过全局注册、`defineAsyncComponent` 或动态导入触达的组件无法自动注册;请通过 `hydrateIslands({ components })` 手动传入(手动项优先)。
|
|
233
|
+
- 只有真正与 `v-client.*` 指令一起使用的组件才会进入客户端产物。
|
|
234
|
+
|
|
235
|
+
端到端流程:**扫描** `.vue` 文件 → 把岛屿标签**转换**为 `<ubean-island v-once>`(props 序列化进 data 属性)→ **解析**导入路径 → **生成**注册表虚拟模块 → 在 `@ubean/client` 中通过 `hydrateIslands` **桥接** → mount 之后(双重 `requestAnimationFrame`)以及每次 SPA 导航后**自动水合**。
|
|
236
|
+
|
|
237
|
+
## 服务端组件端点
|
|
238
|
+
|
|
239
|
+
`createServerComponentMiddleware()` 处理 `POST /__server-component` —— 客户端岛屿包装器需要最新服务端 HTML 时请求的端点:
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
import { SERVER_COMPONENT_ENDPOINT, createServerComponentMiddleware } from '@ubean/islands/server';
|
|
243
|
+
|
|
244
|
+
SERVER_COMPONENT_ENDPOINT; // '/__server-component'
|
|
245
|
+
app.use('*', createServerComponentMiddleware());
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
- 请求体:`{ "path": "/abs/path/Foo.server.vue", "props": { … } }`。
|
|
249
|
+
- 响应:`200` HTML / `400` 缺少 path / `404` 组件未注册 / `500` 渲染失败;每个响应都带 `Cache-Control: no-store`。
|
|
250
|
+
- `registerServerComponent(path, component)` / `getServerComponent(path)` 是 SSR 渲染器与中间件共用的注册表读写口。
|
|
251
|
+
- 注册表刻意挂在 **`globalThis`** 上:dev 下 SSR 图会把本包 runtime 内联,而 `@ubean/app` 通过 Node 解析 `@ubean/islands/server`,模块级 `Map` 会让两个实例拿到不同的注册表 —— 中间件会对已注册的组件返回 404(守卫见 `packages/islands/test/server-component-registry-singleton.test.ts`)。
|
|
252
|
+
- `isServerComponentRequest(c)` / `isServerComponentResponse(response)` 与 `SERVER_COMPONENT_RESPONSE_HEADER`(`x-ubean-server-component`)让请求日志或代理层能识别这些调用。
|
|
253
|
+
|
|
254
|
+
## Vite 插件 API
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import { ubeanIslandsPlugin } from '@ubean/islands/vite';
|
|
258
|
+
|
|
259
|
+
export default {
|
|
260
|
+
plugins: [ubeanIslandsPlugin({ enabled: true })]
|
|
261
|
+
};
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
`UbeanIslandsPluginOptions = { enabled?: boolean }`。该子路径还暴露转换的构建块供高级集成使用:`isServerComponentFile`、`isClientComponentFile`、`hasServerComponentTemplate`、`wrapServerComponentTemplate`、`findRestrictedServerComponentUsage`、`injectServerComponentPath`、`transformVueSfcIslands`、`collectIslandComponents`、`generateRegistryModule`、`parseScriptImports`、`resolveIslandImportPath`、`scanIslandDirectiveNames`,以及虚拟 id `ISLANDS_REGISTRY_VIRTUAL_ID`、`SERVER_COMPONENT_STUB_VIRTUAL_ID`、`CLIENT_COMPONENT_PLACEHOLDER_VIRTUAL_ID`。
|
|
265
|
+
|
|
266
|
+
插件刻意**不**从主入口 re-export —— 它会拉入 `node:fs` / `node:path`,绝不能进入浏览器产物。
|
|
267
|
+
|
|
268
|
+
## SSR 助手
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import { createIslandsContext, registerIsland, getIslandsScript, renderIslandPlaceholder } from '@ubean/islands';
|
|
272
|
+
|
|
273
|
+
const ctx = createIslandsContext();
|
|
274
|
+
const id = registerIsland(ctx, 'Counter', 'client:load', { initial: 10 });
|
|
275
|
+
const html = renderIslandPlaceholder({ component: 'Counter', directive: 'client:load', props: { initial: 10 } });
|
|
276
|
+
const script = getIslandsScript([...ctx.islands.values()]);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
- `getIslandsBootstrapScript()` 产出按策略标记待水合岛屿的 IIFE;`getIslandsClearScript()` 是它的对应物。
|
|
280
|
+
- `hydrationStrategyMeta` 把每条指令映射到其策略元数据。
|
|
281
|
+
- 这些面向自定义 SSR 管线;框架自带渲染器已经调用过它们。
|
|
282
|
+
|
|
283
|
+
## 类型安全
|
|
284
|
+
|
|
285
|
+
- `ClientStrategy` / `ClientDirective` / `ClientDirectiveBinding` / `VClientDirective` 描述指令;`IslandStrategy` / `IslandOptions` / `ServerIslandOptions` 描述包装器。
|
|
286
|
+
- `IslandRecord` / `IslandHydrateOptions` / `HydrateIslandsOptions` / `IslandHydrationScheduler` 描述水合运行时;`IslandsContext` / `IslandDefinition` / `IslandSsrOptions` 描述 SSR 助手。
|
|
287
|
+
- `declare module 'virtual:ubean-islands-registry'` 随包发布,虚拟模块无需额外配置即可获得类型。
|
|
288
|
+
|
|
289
|
+
## 架构说明
|
|
290
|
+
|
|
291
|
+
- **两层,一件事**:`v-client.*` 指令是编写面;注册表加水合运行时是机制。正常应用里你永远不会手写 `hydrateIslands()`。
|
|
292
|
+
- **主入口浏览器安全**:`dist/index.js` 只导入 `./directive` 与 `./runtime`。Vite 插件(`/vite`)与 Hono 中间件(`/server`)是独立子路径。
|
|
293
|
+
- **注册表挂在 `globalThis`**:因为 dev 会加载两份模块实例(见上文);模块作用域的 `Map` 会静默把注册表一分为二。
|
|
294
|
+
- **转换对 HTML 解析刻意克制**:只拒绝解析器确实会移动或丢弃包裹元素的父级;`ul` / `ol` / `dl` 被放过以避免误报。
|
|
295
|
+
- **转换是渐进增强的**:插件在构建期报错,而不是让水合在生产环境静默移除内容。
|
|
296
|
+
- **归属**:`@ubean/build` 接线插件;`@ubean/client` 拥有自动水合调用;`@ubean/islands` 拥有机制。
|
package/dist/runtime.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ubean/islands",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "Islands architecture for ubean (Vite plugin for client:load/idle/visible/media/only directives, bootstrap script, client hydrate runtime)",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -36,19 +36,19 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@ubean/shared": "0.6.
|
|
39
|
+
"@ubean/shared": "0.6.4",
|
|
40
40
|
"hono": "4.13.13",
|
|
41
41
|
"pathe": "^2.0.3",
|
|
42
|
-
"vue": "^3.5.
|
|
42
|
+
"vue": "^3.5.44"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^26.6.5",
|
|
46
46
|
"typescript": "npm:typescript-native-bridge@latest",
|
|
47
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@1.
|
|
48
|
-
"vite-plus": "1.
|
|
47
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.3.0",
|
|
48
|
+
"vite-plus": "1.3.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@1.
|
|
51
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.3.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"vite": {
|