@ubean/islands 0.2.1 → 0.3.0

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/dist/runtime.d.ts CHANGED
@@ -25,6 +25,25 @@ interface IslandRecord {
25
25
  mediaQuery?: string;
26
26
  props: Record<string, unknown>;
27
27
  }
28
+ /**
29
+ * True when the tree still has islands that have not been hydrated.
30
+ * Used by the client entry to skip the second rAF on SPA navigations
31
+ * that did not introduce new islands.
32
+ */
33
+ declare function hasPendingIslands(root?: DomParentNode | null): boolean;
34
+ interface IslandHydrationScheduler {
35
+ requestAnimationFrame: (cb: () => void) => unknown;
36
+ hasPending: () => boolean;
37
+ hydrate: () => void;
38
+ /**
39
+ * First mount: always wait two frames so Vue's patch (and async resolve)
40
+ * finishes. SPA `afterEach`: after the first frame, skip the second when
41
+ * nothing is pending — nested Suspense islands that appear only on the
42
+ * second frame are a documented miss; first mount still forces two frames.
43
+ */
44
+ forceDoubleFrame?: boolean;
45
+ }
46
+ declare function scheduleIslandHydration(options: IslandHydrationScheduler): void;
28
47
  declare function collectIslands(root?: DomParentNode): IslandRecord[];
29
48
  declare function hydrateIsland(record: IslandRecord, component: Component, options?: IslandHydrateOptions): void;
30
49
  interface HydrateIslandsOptions extends IslandHydrateOptions {
@@ -335,4 +354,4 @@ declare function defineClientComponent(component: Component): Component;
335
354
  */
336
355
  declare function definePairedComponent(ServerComp: Component, ClientComp: Component): Component;
337
356
  //#endregion
338
- export { ClientComponentPlaceholder, HydrateIslandsOptions, IslandHydrateOptions, IslandOptions, IslandRecord, IslandStrategy, SERVER_COMPONENT_ENDPOINT, ServerComponentStub, ServerIslandOptions, _clearServerComponentRegistry, collectIslands, defineClientComponent, defineIsland, definePairedComponent, defineServerIsland, getServerComponent, hydrateIsland, hydrateIslands, registerServerComponent };
357
+ export { ClientComponentPlaceholder, HydrateIslandsOptions, IslandHydrateOptions, IslandHydrationScheduler, IslandOptions, IslandRecord, IslandStrategy, SERVER_COMPONENT_ENDPOINT, ServerComponentStub, ServerIslandOptions, _clearServerComponentRegistry, collectIslands, defineClientComponent, defineIsland, definePairedComponent, defineServerIsland, getServerComponent, hasPendingIslands, hydrateIsland, hydrateIslands, registerServerComponent, scheduleIslandHydration };
package/dist/runtime.js CHANGED
@@ -9,6 +9,24 @@ function decodeProps(raw) {
9
9
  return {};
10
10
  }
11
11
  }
12
+ /**
13
+ * True when the tree still has islands that have not been hydrated.
14
+ * Used by the client entry to skip the second rAF on SPA navigations
15
+ * that did not introduce new islands.
16
+ */
17
+ function hasPendingIslands(root) {
18
+ return collectIslands(root ?? void 0).some((record) => !record.el.hasAttribute("data-hydrated"));
19
+ }
20
+ function scheduleIslandHydration(options) {
21
+ const raf = options.requestAnimationFrame;
22
+ raf(() => {
23
+ if (!options.forceDoubleFrame && !options.hasPending()) return;
24
+ raf(() => {
25
+ if (!options.hasPending()) return;
26
+ options.hydrate();
27
+ });
28
+ });
29
+ }
12
30
  function collectIslands(root) {
13
31
  const doc = root ?? _global.document;
14
32
  if (!doc) return [];
@@ -55,6 +73,7 @@ function hydrateIslands(options = {}) {
55
73
  const islands = collectIslands(root);
56
74
  if (islands.length === 0) return;
57
75
  for (const record of islands) {
76
+ if (record.el.hasAttribute("data-hydrated")) continue;
58
77
  if (record.directive === "client:only") {
59
78
  const comp = resolveComponent(record.componentName, components, getComponent);
60
79
  if (comp) Promise.resolve(comp).then((c) => hydrateIsland(record, c, rest));
@@ -504,4 +523,4 @@ function definePairedComponent(ServerComp, ClientComp) {
504
523
  });
505
524
  }
506
525
  //#endregion
507
- export { ClientComponentPlaceholder, SERVER_COMPONENT_ENDPOINT, ServerComponentStub, _clearServerComponentRegistry, collectIslands, defineClientComponent, defineIsland, definePairedComponent, defineServerIsland, getServerComponent, hydrateIsland, hydrateIslands, registerServerComponent };
526
+ export { ClientComponentPlaceholder, SERVER_COMPONENT_ENDPOINT, ServerComponentStub, _clearServerComponentRegistry, collectIslands, defineClientComponent, defineIsland, definePairedComponent, defineServerIsland, getServerComponent, hasPendingIslands, hydrateIsland, hydrateIslands, registerServerComponent, scheduleIslandHydration };
package/dist/vite.js CHANGED
@@ -133,7 +133,7 @@ const DEFINE_SERVER_ISLAND_RE = /\bdefineServerIsland\s*\(/g;
133
133
  * 预扫描代码,返回所有"非代码"区间 `[start, end)`:注释(块/行)和字符串字面量。
134
134
  *
135
135
  * 用于跳过注释/字符串内的 `defineServerIsland(` 匹配(如 JSDoc 示例或说明性字符串)。
136
- * 复用 `@ubean/actions` Vite 插件的成熟模式 (见 lessons #P-lessons)。
136
+ * 复用 `@ubean/build/actions` Vite 插件的成熟模式 (见 lessons #P-lessons)。
137
137
  */
138
138
  function findNonCodeRanges(code) {
139
139
  const ranges = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubean/islands",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
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"
@@ -38,7 +38,7 @@
38
38
  "dependencies": {
39
39
  "hono": "4.13.3",
40
40
  "vue": "^3.5.41",
41
- "@ubean/shared": "0.2.1"
41
+ "@ubean/shared": "0.3.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^26.2.0",