dtwo 3.0.0 → 3.1.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.
Files changed (61) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +6 -2
  3. package/bin/dtwo.mjs +1 -1
  4. package/config.d.ts +1 -1
  5. package/dist/app/compat/idle-callback.d.ts +2 -0
  6. package/dist/app/compat/idle-callback.mjs +13 -0
  7. package/dist/app/components/dtwo-island.d.ts +33 -0
  8. package/dist/app/components/dtwo-island.mjs +62 -0
  9. package/dist/app/components/dtwo-link.d.ts +3 -3
  10. package/dist/app/components/dtwo-link.mjs +26 -28
  11. package/dist/app/components/dtwo-loading-indicator.d.ts +3 -3
  12. package/dist/app/components/dtwo-loading-indicator.mjs +8 -7
  13. package/dist/app/components/dtwo-root.kdu +10 -2
  14. package/dist/app/components/island-renderer.d.ts +20 -0
  15. package/dist/app/components/island-renderer.mjs +26 -0
  16. package/dist/app/components/layout.d.ts +1 -1
  17. package/dist/app/components/layout.mjs +12 -3
  18. package/dist/app/components/utils.mjs +1 -0
  19. package/dist/app/composables/asyncData.d.ts +4 -1
  20. package/dist/app/composables/asyncData.mjs +15 -2
  21. package/dist/app/composables/component.d.ts +1 -1
  22. package/dist/app/composables/cookie.d.ts +5 -4
  23. package/dist/app/composables/cookie.mjs +8 -2
  24. package/dist/app/composables/error.d.ts +1 -1
  25. package/dist/app/composables/fetch.d.ts +6 -6
  26. package/dist/app/composables/fetch.mjs +2 -1
  27. package/dist/app/composables/index.d.ts +2 -1
  28. package/dist/app/composables/index.mjs +2 -1
  29. package/dist/app/composables/ready.d.ts +1 -0
  30. package/dist/app/composables/ready.mjs +12 -0
  31. package/dist/app/composables/router.d.ts +1 -1
  32. package/dist/app/composables/router.mjs +5 -0
  33. package/dist/app/composables/ssr.d.ts +1 -1
  34. package/dist/app/config.mjs +1 -0
  35. package/dist/app/dtwo.d.ts +5 -4
  36. package/dist/app/plugins/cross-origin-prefetch.client.mjs +19 -14
  37. package/dist/app/plugins/router.mjs +13 -5
  38. package/dist/app/types/augments.d.ts +1 -1
  39. package/dist/components/runtime/server-component.d.ts +3 -0
  40. package/dist/components/runtime/server-component.mjs +87 -0
  41. package/dist/core/runtime/hole/renderer.d.ts +19 -0
  42. package/dist/core/runtime/hole/renderer.mjs +71 -9
  43. package/dist/head/runtime/components.d.ts +14 -14
  44. package/dist/head/runtime/components.mjs +12 -1
  45. package/dist/head/runtime/composables.d.ts +17 -0
  46. package/dist/head/runtime/composables.mjs +9 -0
  47. package/dist/head/runtime/lib/kduse-head.plugin.mjs +2 -1
  48. package/dist/index.mjs +298 -77
  49. package/dist/pages/runtime/composables.d.ts +1 -1
  50. package/dist/pages/runtime/page.d.ts +5 -1
  51. package/dist/pages/runtime/page.mjs +6 -3
  52. package/dist/pages/runtime/plugins/prefetch.client.mjs +36 -0
  53. package/dist/pages/runtime/{router.d.ts → plugins/router.d.ts} +1 -1
  54. package/dist/pages/runtime/{router.mjs → plugins/router.mjs} +11 -8
  55. package/dist/pages/runtime/utils.d.ts +1 -1
  56. package/dist/pages/runtime/utils.mjs +1 -1
  57. package/dist/pages/runtime/validate.mjs +4 -4
  58. package/package.json +51 -51
  59. package/types.d.ts +3 -0
  60. package/dist/head/runtime/lib/kdu-meta.plugin.mjs +0 -31
  61. /package/dist/{head/runtime/lib/kdu-meta.plugin.d.ts → pages/runtime/plugins/prefetch.client.d.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  export { defineDtwoComponent } from './component';
2
- export { useAsyncData, useLazyAsyncData, refreshDtwoData, clearDtwoData } from './asyncData';
2
+ export { useAsyncData, useLazyAsyncData, useDtwoData, refreshDtwoData, clearDtwoData } from './asyncData';
3
3
  export type { AsyncDataOptions, AsyncData } from './asyncData';
4
4
  export { useHydration } from './hydrate';
5
5
  export { useState } from './state';
@@ -10,6 +10,7 @@ export type { FetchResult, UseFetchOptions } from './fetch';
10
10
  export { useCookie } from './cookie';
11
11
  export type { CookieOptions, CookieRef } from './cookie';
12
12
  export { useRequestHeaders, useRequestEvent, setResponseStatus } from './ssr';
13
+ export { onDtwoReady } from './ready';
13
14
  export { abortNavigation, addRouteMiddleware, defineDtwoRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter } from './router';
14
15
  export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router';
15
16
  export { preloadComponents, prefetchComponents, preloadRouteComponents } from './preload';
@@ -1,11 +1,12 @@
1
1
  export { defineDtwoComponent } from "./component.mjs";
2
- export { useAsyncData, useLazyAsyncData, refreshDtwoData, clearDtwoData } from "./asyncData.mjs";
2
+ export { useAsyncData, useLazyAsyncData, useDtwoData, refreshDtwoData, clearDtwoData } from "./asyncData.mjs";
3
3
  export { useHydration } from "./hydrate.mjs";
4
4
  export { useState } from "./state.mjs";
5
5
  export { clearError, createError, isDtwoError, showError, useError } from "./error.mjs";
6
6
  export { useFetch, useLazyFetch } from "./fetch.mjs";
7
7
  export { useCookie } from "./cookie.mjs";
8
8
  export { useRequestHeaders, useRequestEvent, setResponseStatus } from "./ssr.mjs";
9
+ export { onDtwoReady } from "./ready.mjs";
9
10
  export { abortNavigation, addRouteMiddleware, defineDtwoRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter } from "./router.mjs";
10
11
  export { preloadComponents, prefetchComponents, preloadRouteComponents } from "./preload.mjs";
11
12
  export { isPrerendered, loadPayload, preloadPayload } from "./payload.mjs";
@@ -0,0 +1 @@
1
+ export declare const onDtwoReady: (callback: () => any) => void;
@@ -0,0 +1,12 @@
1
+ import { useDtwoApp } from "../dtwo.mjs";
2
+ import { requestIdleCallback } from "../compat/idle-callback.mjs";
3
+ export const onDtwoReady = (callback) => {
4
+ const dtwoApp = useDtwoApp();
5
+ if (dtwoApp.isHydrating) {
6
+ dtwoApp.hooks.hookOnce("app:suspense:resolve", () => {
7
+ requestIdleCallback(callback);
8
+ });
9
+ } else {
10
+ requestIdleCallback(callback);
11
+ }
12
+ };
@@ -1,5 +1,5 @@
1
1
  import type { NavigationGuard, RouteLocationNormalized, RouteLocationRaw, NavigationFailure } from 'kdu-router';
2
- import { DtwoError } from './error';
2
+ import type { DtwoError } from './error';
3
3
  export declare const useRouter: () => Router;
4
4
  export declare const useRoute: () => RouteLocationNormalizedLoaded;
5
5
  export declare const onBeforeRouteLeave: (guard: NavigationGuard) => void;
@@ -4,6 +4,7 @@ import { hasProtocol, joinURL, parseURL } from "ufo";
4
4
  import { useDtwoApp, useRuntimeConfig } from "../dtwo.mjs";
5
5
  import { createError } from "./error.mjs";
6
6
  import { useState } from "./state.mjs";
7
+ import { setResponseStatus } from "./ssr.mjs";
7
8
  export const useRouter = () => {
8
9
  return useDtwoApp()?.$router;
9
10
  };
@@ -64,6 +65,10 @@ export const navigateTo = (to, options) => {
64
65
  if (process.server) {
65
66
  const dtwoApp = useDtwoApp();
66
67
  if (dtwoApp.ssrContext && dtwoApp.ssrContext.event) {
68
+ if (isProcessingMiddleware() && !isExternal) {
69
+ setResponseStatus(options?.redirectCode || 302);
70
+ return to;
71
+ }
67
72
  const redirectLocation = isExternal ? toPath : joinURL(useRuntimeConfig().app.baseURL, router.resolve(to).fullPath || "/");
68
73
  return dtwoApp.callHook("app:redirected").then(() => sendRedirect(dtwoApp.ssrContext.event, redirectLocation, options?.redirectCode || 302));
69
74
  }
@@ -1,5 +1,5 @@
1
1
  import type { H3Event } from 'h3';
2
- import { DtwoApp } from '../dtwo';
2
+ import type { DtwoApp } from '../dtwo';
3
3
  export declare function useRequestHeaders<K extends string = string>(include: K[]): Record<Lowercase<K>, string | undefined>;
4
4
  export declare function useRequestHeaders(): Readonly<Record<string, string | undefined>>;
5
5
  export declare function useRequestEvent(dtwoApp?: DtwoApp): H3Event;
@@ -17,6 +17,7 @@ function deepAssign(obj, newObj) {
17
17
  for (const key in newObj) {
18
18
  const val = newObj[key];
19
19
  if (val !== null && typeof val === "object") {
20
+ obj[key] = obj[key] || {};
20
21
  deepAssign(obj[key], val);
21
22
  } else {
22
23
  obj[key] = val;
@@ -1,9 +1,9 @@
1
- import { Ref } from 'kdu';
2
- import type { App, onErrorCaptured, KNode } from 'kdu';
3
- import { Hookable } from 'hookable';
1
+ import type { App, onErrorCaptured, KNode, Ref } from 'kdu';
2
+ import type { Hookable } from 'hookable';
4
3
  import type { RuntimeConfig, AppConfigInput } from '@dtwo/schema';
5
4
  import type { SSRContext } from 'kdu-bundle-renderer/runtime';
6
5
  import type { H3Event } from 'h3';
6
+ import type { DtwoIslandContext } from '../core/runtime/hole/renderer';
7
7
  type DtwoMeta = {
8
8
  htmlAttrs?: string;
9
9
  headAttrs?: string;
@@ -46,6 +46,7 @@ export interface DtwoSSRContext extends SSRContext {
46
46
  payload: _DtwoApp['payload'];
47
47
  teleports?: Record<string, string>;
48
48
  renderMeta?: () => Promise<DtwoMeta> | DtwoMeta;
49
+ islandContext?: DtwoIslandContext;
49
50
  }
50
51
  interface _DtwoApp {
51
52
  kduApp: App<Element>;
@@ -112,7 +113,7 @@ export declare function isDtwoPlugin(plugin: unknown): boolean;
112
113
  * @param dtwo A Dtwo instance
113
114
  * @param setup The function to call
114
115
  */
115
- export declare function callWithDtwo<T extends (...args: any[]) => any>(dtwo: DtwoApp | _DtwoApp, setup: T, args?: Parameters<T>): any;
116
+ export declare function callWithDtwo<T extends (...args: any[]) => any>(dtwo: DtwoApp | _DtwoApp, setup: T, args?: Parameters<T>): ReturnType<T> | Promise<ReturnType<T>>;
116
117
  /**
117
118
  * Returns the current Dtwo instance.
118
119
  */
@@ -3,26 +3,31 @@ import { parseURL } from "ufo";
3
3
  import { defineDtwoPlugin, useHead } from "#app";
4
4
  export default defineDtwoPlugin((dtwoApp) => {
5
5
  const externalURLs = ref(/* @__PURE__ */ new Set());
6
- useHead({
7
- script: [
8
- () => ({
9
- type: "speculationrules",
10
- innerHTML: JSON.stringify({
11
- prefetch: [
12
- {
13
- source: "list",
14
- urls: [...externalURLs.value],
15
- requires: ["anonymous-client-ip-when-cross-origin"]
16
- }
17
- ]
18
- })
6
+ function generateRules() {
7
+ return {
8
+ type: "speculationrules",
9
+ key: "speculationrules",
10
+ innerHTML: JSON.stringify({
11
+ prefetch: [
12
+ {
13
+ source: "list",
14
+ urls: [...externalURLs.value],
15
+ requires: ["anonymous-client-ip-when-cross-origin"]
16
+ }
17
+ ]
19
18
  })
20
- ]
19
+ };
20
+ }
21
+ const head = useHead({
22
+ script: [generateRules()]
21
23
  });
22
24
  dtwoApp.hook("link:prefetch", (url) => {
23
25
  const { protocol } = parseURL(url);
24
26
  if (protocol && ["http:", "https:"].includes(protocol)) {
25
27
  externalURLs.value.add(url);
28
+ head?.patch({
29
+ script: [generateRules()]
30
+ });
26
31
  }
27
32
  });
28
33
  });
@@ -1,7 +1,7 @@
1
- import { reactive, h } from "kdu";
1
+ import { reactive, h, isReadonly } from "kdu";
2
2
  import { parseURL, stringifyParsedURL, parseQuery, stringifyQuery, withoutBase, isEqual, joinURL } from "ufo";
3
3
  import { createError } from "h3";
4
- import { defineDtwoPlugin, clearError, navigateTo, showError, useRuntimeConfig, useState } from "../index.mjs";
4
+ import { defineDtwoPlugin, clearError, navigateTo, showError, useRuntimeConfig, useState, useRequestEvent } from "../index.mjs";
5
5
  import { callWithDtwo } from "../dtwo.mjs";
6
6
  import { globalMiddleware } from "#build/middleware";
7
7
  function getRouteFromPath(fullPath) {
@@ -18,6 +18,7 @@ function getRouteFromPath(fullPath) {
18
18
  fullPath,
19
19
  query: parseQuery(url.search),
20
20
  hash: url.hash,
21
+ // stub properties for compat with kdu-router
21
22
  params: {},
22
23
  name: void 0,
23
24
  matched: [],
@@ -78,17 +79,21 @@ export default defineDtwoPlugin((dtwoApp) => {
78
79
  const router = {
79
80
  currentRoute: route,
80
81
  isReady: () => Promise.resolve(),
82
+ // These options provide a similar API to kdu-router but have no effect
81
83
  options: {},
82
84
  install: () => Promise.resolve(),
85
+ // Navigation
83
86
  push: (url) => handleNavigation(url, false),
84
87
  replace: (url) => handleNavigation(url, true),
85
88
  back: () => window.history.go(-1),
86
89
  go: (delta) => window.history.go(delta),
87
90
  forward: () => window.history.go(1),
91
+ // Guards
88
92
  beforeResolve: (guard) => registerHook("resolve:before", guard),
89
93
  beforeEach: (guard) => registerHook("navigate:before", guard),
90
94
  afterEach: (guard) => registerHook("navigate:after", guard),
91
95
  onError: (handler) => registerHook("error", handler),
96
+ // Routes
92
97
  resolve: getRouteFromPath,
93
98
  addRoute: (parentName, route2) => {
94
99
  routes.push(route2);
@@ -108,6 +113,7 @@ export default defineDtwoPlugin((dtwoApp) => {
108
113
  to: String,
109
114
  custom: Boolean,
110
115
  replace: Boolean,
116
+ // Not implemented
111
117
  activeClass: String,
112
118
  exactActiveClass: String,
113
119
  ariaCurrentValue: String
@@ -138,8 +144,8 @@ export default defineDtwoPlugin((dtwoApp) => {
138
144
  dtwoApp.hooks.hookOnce("app:created", async () => {
139
145
  router.beforeEach(async (to, from) => {
140
146
  to.meta = reactive(to.meta || {});
141
- if (dtwoApp.isHydrating) {
142
- to.meta.layout = initialLayout.value ?? to.meta.layout;
147
+ if (dtwoApp.isHydrating && initialLayout.value && !isReadonly(to.meta.layout)) {
148
+ to.meta.layout = initialLayout.value;
143
149
  }
144
150
  dtwoApp._processingMiddleware = true;
145
151
  const middlewareEntries = /* @__PURE__ */ new Set([...globalMiddleware, ...dtwoApp._middleware.global]);
@@ -164,7 +170,9 @@ export default defineDtwoPlugin((dtwoApp) => {
164
170
  });
165
171
  await router.replace(initialURL);
166
172
  if (!isEqual(route.fullPath, initialURL)) {
167
- await callWithDtwo(dtwoApp, navigateTo, [route.fullPath]);
173
+ const event = await callWithDtwo(dtwoApp, useRequestEvent);
174
+ const options = { redirectCode: event.node.res.statusCode !== 200 ? event.node.res.statusCode || 302 : 302 };
175
+ await callWithDtwo(dtwoApp, navigateTo, [route.fullPath, options]);
168
176
  }
169
177
  });
170
178
  return {
@@ -1,4 +1,4 @@
1
- import { DtwoApp } from '../dtwo'
1
+ import type { DtwoApp } from '../dtwo'
2
2
 
3
3
  declare global {
4
4
  namespace NodeJS {
@@ -0,0 +1,3 @@
1
+ export declare const createServerComponent: (name: string) => import("kdu").DefineComponent<{}, () => import("kdu").KNode<import("kdu").RendererNode, import("kdu").RendererElement, {
2
+ [key: string]: any;
3
+ }>, {}, {}, {}, import("kdu").ComponentOptionsMixin, import("kdu").ComponentOptionsMixin, {}, string, import("kdu").KNodeProps & import("kdu").AllowedComponentProps & import("kdu").ComponentCustomProps, Readonly<import("kdu").ExtractPropTypes<{}>>, {}>;
@@ -0,0 +1,87 @@
1
+ import { defineComponent, createStaticKNode, computed, h, watch } from "kdu";
2
+ import { debounce } from "perfect-debounce";
3
+ import { hash } from "ohash";
4
+ import { appendHeader } from "h3";
5
+ import { useAsyncData, useHead, useDtwoApp, useRequestEvent } from "#app";
6
+ const pKey = "_islandPromises";
7
+ export const createServerComponent = (name) => {
8
+ return defineComponent({
9
+ name,
10
+ inheritAttrs: false,
11
+ setup(_props, { attrs }) {
12
+ return () => h(DtwoServerComponent, {
13
+ name,
14
+ props: attrs
15
+ });
16
+ }
17
+ });
18
+ };
19
+ const DtwoServerComponent = defineComponent({
20
+ name: "DtwoServerComponent",
21
+ props: {
22
+ name: {
23
+ type: String,
24
+ required: true
25
+ },
26
+ props: {
27
+ type: Object,
28
+ default: () => void 0
29
+ },
30
+ context: {
31
+ type: Object,
32
+ default: () => ({})
33
+ }
34
+ },
35
+ async setup(props) {
36
+ const dtwoApp = useDtwoApp();
37
+ const hashId = computed(() => hash([props.name, props.props, props.context]));
38
+ const event = useRequestEvent();
39
+ function _fetchComponent() {
40
+ const url = `/__dtwo_island/${props.name}:${hashId.value}`;
41
+ if (process.server && process.env.prerender) {
42
+ appendHeader(event, "x-hole-prerender", url);
43
+ }
44
+ return $fetch(url, {
45
+ params: {
46
+ ...props.context,
47
+ props: props.props ? JSON.stringify(props.props) : void 0
48
+ }
49
+ });
50
+ }
51
+ const res = useAsyncData(
52
+ `${props.name}:${hashId.value}`,
53
+ async () => {
54
+ dtwoApp[pKey] = dtwoApp[pKey] || {};
55
+ if (!dtwoApp[pKey][hashId.value]) {
56
+ dtwoApp[pKey][hashId.value] = _fetchComponent().finally(() => {
57
+ delete dtwoApp[pKey][hashId.value];
58
+ });
59
+ }
60
+ const res2 = await dtwoApp[pKey][hashId.value];
61
+ return {
62
+ html: res2.html,
63
+ head: {
64
+ link: res2.head.link,
65
+ style: res2.head.style
66
+ }
67
+ };
68
+ },
69
+ {
70
+ immediate: process.server || !dtwoApp.isHydrating,
71
+ default: () => ({
72
+ html: "",
73
+ head: {
74
+ link: [],
75
+ style: []
76
+ }
77
+ })
78
+ }
79
+ );
80
+ useHead(() => res.data.value.head);
81
+ if (process.client) {
82
+ watch(props, debounce(() => res.execute(), 100));
83
+ }
84
+ await res;
85
+ return () => createStaticKNode(res.data.value.html, 1);
86
+ }
87
+ });
@@ -1,4 +1,5 @@
1
1
  export interface DtwoRenderHTMLContext {
2
+ island?: boolean;
2
3
  htmlAttrs: string[];
3
4
  head: string[];
4
5
  bodyAttrs: string[];
@@ -6,6 +7,24 @@ export interface DtwoRenderHTMLContext {
6
7
  body: string[];
7
8
  bodyAppend: string[];
8
9
  }
10
+ export interface DtwoIslandContext {
11
+ id?: string;
12
+ name: string;
13
+ props?: Record<string, any>;
14
+ url?: string;
15
+ }
16
+ export interface DtwoIslandResponse {
17
+ id?: string;
18
+ html: string;
19
+ state: Record<string, any>;
20
+ head: {
21
+ link: (Record<string, string>)[];
22
+ style: ({
23
+ innerHTML: string;
24
+ key: string;
25
+ })[];
26
+ };
27
+ }
9
28
  export interface DtwoRenderResponse {
10
29
  body: string;
11
30
  statusCode: number;
@@ -1,9 +1,11 @@
1
1
  import { createRenderer, renderResourceHeaders } from "kdu-bundle-renderer/runtime";
2
- import { appendHeader, createError, getQuery, writeEarlyHints } from "h3";
2
+ import { appendHeader, getQuery, writeEarlyHints, readBody, createError } from "h3";
3
3
  import devalue from "@d2js/devalue";
4
+ import destr from "destr";
4
5
  import { joinURL } from "ufo";
5
6
  import { renderToString as _renderToString } from "kdu/server-renderer";
6
7
  import { useRuntimeConfig, useHoleApp, defineRenderHandler, getRouteRules } from "#internal/hole";
8
+ import { hash } from "ohash";
7
9
  import { appRootId, appRootTag } from "#internal/dtwo.config.mjs";
8
10
  import { buildAssetsURL, publicAssetsURL } from "#paths";
9
11
  globalThis.__buildAssetsURL = buildAssetsURL;
@@ -65,15 +67,30 @@ const getSPARenderer = lazyCachedFunction(async () => {
65
67
  renderToString
66
68
  };
67
69
  });
70
+ async function getIslandContext(event) {
71
+ const url = event.node.req.url?.substring("/__dtwo_island".length + 1) || "";
72
+ const [componentName, hashId] = url.split("?")[0].split(":");
73
+ const context = event.node.req.method === "GET" ? getQuery(event) : await readBody(event);
74
+ const ctx = {
75
+ url: "/",
76
+ ...context,
77
+ id: hashId,
78
+ name: componentName,
79
+ props: destr(context.props) || {}
80
+ };
81
+ return ctx;
82
+ }
68
83
  const PAYLOAD_CACHE = process.env.DTWO_PAYLOAD_EXTRACTION && process.env.prerender ? /* @__PURE__ */ new Map() : null;
69
84
  const PAYLOAD_URL_RE = /\/_payload(\.[a-zA-Z0-9]+)?.js(\?.*)?$/;
70
85
  const PRERENDER_NO_SSR_ROUTES = /* @__PURE__ */ new Set(["/index.html", "/200.html", "/404.html"]);
71
86
  export default defineRenderHandler(async (event) => {
87
+ const holeApp = useHoleApp();
72
88
  const ssrError = event.node.req.url?.startsWith("/__dtwo_error") ? getQuery(event) : null;
73
89
  if (ssrError && event.node.req.socket.readyState !== "readOnly") {
74
90
  throw createError("Cannot directly render error page!");
75
91
  }
76
- let url = ssrError?.url || event.node.req.url;
92
+ const islandContext = process.env.DTWO_COMPONENT_ISLANDS && event.node.req.url?.startsWith("/__dtwo_island") ? await getIslandContext(event) : void 0;
93
+ let url = ssrError?.url || islandContext?.url || event.node.req.url;
77
94
  const isRenderingPayload = PAYLOAD_URL_RE.test(url);
78
95
  if (isRenderingPayload) {
79
96
  url = url.substring(0, url.lastIndexOf("/")) || "/";
@@ -90,7 +107,9 @@ export default defineRenderHandler(async (event) => {
90
107
  noSSR: !!process.env.DTWO_NO_SSR || !!event.node.req.headers["x-dtwo-no-ssr"] || routeOptions.ssr === false || (process.env.prerender ? PRERENDER_NO_SSR_ROUTES.has(url) : false),
91
108
  error: !!ssrError,
92
109
  dtwo: void 0,
93
- payload: ssrError ? { error: ssrError } : {}
110
+ /* DtwoApp */
111
+ payload: ssrError ? { error: ssrError } : {},
112
+ islandContext
94
113
  };
95
114
  const _PAYLOAD_EXTRACTION = process.env.prerender && process.env.DTWO_PAYLOAD_EXTRACTION && !ssrContext.noSSR;
96
115
  const payloadURL = _PAYLOAD_EXTRACTION ? joinURL(useRuntimeConfig().app.baseURL, url, "_payload.js") : void 0;
@@ -123,6 +142,7 @@ export default defineRenderHandler(async (event) => {
123
142
  const renderedMeta = await ssrContext.renderMeta?.() ?? {};
124
143
  const inlinedStyles = process.env.DTWO_INLINE_STYLES ? await renderInlineStyles(ssrContext.modules ?? ssrContext._registeredComponents ?? []) : "";
125
144
  const htmlContext = {
145
+ island: Boolean(islandContext),
126
146
  htmlAttrs: normalizeChunks([renderedMeta.htmlAttrs]),
127
147
  head: normalizeChunks([
128
148
  renderedMeta.headTags,
@@ -137,24 +157,52 @@ export default defineRenderHandler(async (event) => {
137
157
  renderedMeta.bodyScriptsPrepend,
138
158
  ssrContext.teleports?.body
139
159
  ]),
140
- body: [
141
- _rendered.html
142
- ],
160
+ body: process.env.DTWO_COMPONENT_ISLANDS && islandContext ? [] : [_rendered.html],
143
161
  bodyAppend: normalizeChunks([
144
162
  process.env.DTWO_NO_SCRIPTS ? void 0 : _PAYLOAD_EXTRACTION ? `<script type="module">import p from "${payloadURL}";window.__DTWO__={...p,...(${devalue(splitPayload(ssrContext).initial)})}<\/script>` : `<script>window.__DTWO__=${devalue(ssrContext.payload)}<\/script>`,
145
163
  _rendered.renderScripts(),
164
+ // Note: bodyScripts may contain tags other than <script>
146
165
  renderedMeta.bodyScripts
147
166
  ])
148
167
  };
149
- const holeApp = useHoleApp();
150
168
  await holeApp.hooks.callHook("render:html", htmlContext, { event });
169
+ if (process.env.DTWO_COMPONENT_ISLANDS && islandContext) {
170
+ const _tags = htmlContext.head.flatMap((head2) => extractHTMLTags(head2));
171
+ const head = {
172
+ link: _tags.filter((tag) => tag.tagName === "link" && tag.attrs.rel === "stylesheet" && tag.attrs.href.includes("scoped") && !tag.attrs.href.includes("pages/")).map((tag) => ({
173
+ key: "island-link-" + hash(tag.attrs.href),
174
+ ...tag.attrs
175
+ })),
176
+ style: _tags.filter((tag) => tag.tagName === "style" && tag.innerHTML).map((tag) => ({
177
+ key: "island-style-" + hash(tag.innerHTML),
178
+ innerHTML: tag.innerHTML
179
+ }))
180
+ };
181
+ const islandResponse = {
182
+ id: islandContext.id,
183
+ head,
184
+ html: ssrContext.teleports["dtwo-island"].replace(/<!--.*-->/g, ""),
185
+ state: ssrContext.payload.state
186
+ };
187
+ await holeApp.hooks.callHook("render:island", islandResponse, { event, islandContext });
188
+ const response2 = {
189
+ body: JSON.stringify(islandResponse, null, 2),
190
+ statusCode: event.node.res.statusCode,
191
+ statusMessage: event.node.res.statusMessage,
192
+ headers: {
193
+ "content-type": "application/json;charset=utf-8",
194
+ "x-powered-by": "Dtwo"
195
+ }
196
+ };
197
+ return response2;
198
+ }
151
199
  const response = {
152
200
  body: renderHTMLDocument(htmlContext),
153
201
  statusCode: event.node.res.statusCode,
154
202
  statusMessage: event.node.res.statusMessage,
155
203
  headers: {
156
- "Content-Type": "text/html;charset=UTF-8",
157
- "X-Powered-By": "Dtwo"
204
+ "content-type": "text/html;charset=utf-8",
205
+ "x-powered-by": "Dtwo"
158
206
  }
159
207
  };
160
208
  return response;
@@ -187,6 +235,20 @@ function renderHTMLDocument(html) {
187
235
  <body ${joinAttrs(html.bodyAttrs)}>${joinTags(html.bodyPrepend)}${joinTags(html.body)}${joinTags(html.bodyAppend)}</body>
188
236
  </html>`;
189
237
  }
238
+ const HTML_TAG_RE = /<(?<tag>[a-z]+)(?<rawAttrs> [^>]*)?>(?:(?<innerHTML>[\s\S]*?)<\/\k<tag>)?/g;
239
+ const HTML_TAG_ATTR_RE = /(?<name>[a-z]+)="(?<value>[^"]*)"/g;
240
+ function extractHTMLTags(html) {
241
+ const tags = [];
242
+ for (const tagMatch of html.matchAll(HTML_TAG_RE)) {
243
+ const attrs = {};
244
+ for (const attrMatch of tagMatch.groups.rawAttrs?.matchAll(HTML_TAG_ATTR_RE) || []) {
245
+ attrs[attrMatch.groups.name] = attrMatch.groups.value;
246
+ }
247
+ const innerHTML = tagMatch.groups.innerHTML || "";
248
+ tags.push({ tagName: tagMatch.groups.tag, attrs, innerHTML });
249
+ }
250
+ return tags;
251
+ }
190
252
  async function renderInlineStyles(usedModules) {
191
253
  const styleMap = await getSSRStyles();
192
254
  const inlinedStyles = /* @__PURE__ */ new Set();
@@ -10,7 +10,7 @@ export declare const NoScript: import("kdu").DefineComponent<{
10
10
  type: BooleanConstructor;
11
11
  default: undefined;
12
12
  };
13
- class: StringConstructor;
13
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
14
14
  contenteditable: {
15
15
  type: BooleanConstructor;
16
16
  default: undefined;
@@ -58,7 +58,7 @@ export declare const NoScript: import("kdu").DefineComponent<{
58
58
  type: BooleanConstructor;
59
59
  default: undefined;
60
60
  };
61
- class: StringConstructor;
61
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
62
62
  contenteditable: {
63
63
  type: BooleanConstructor;
64
64
  default: undefined;
@@ -134,7 +134,7 @@ export declare const Link: import("kdu").DefineComponent<{
134
134
  type: BooleanConstructor;
135
135
  default: undefined;
136
136
  };
137
- class: StringConstructor;
137
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
138
138
  contenteditable: {
139
139
  type: BooleanConstructor;
140
140
  default: undefined;
@@ -204,7 +204,7 @@ export declare const Link: import("kdu").DefineComponent<{
204
204
  type: BooleanConstructor;
205
205
  default: undefined;
206
206
  };
207
- class: StringConstructor;
207
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
208
208
  contenteditable: {
209
209
  type: BooleanConstructor;
210
210
  default: undefined;
@@ -259,7 +259,7 @@ export declare const Base: import("kdu").DefineComponent<{
259
259
  type: BooleanConstructor;
260
260
  default: undefined;
261
261
  };
262
- class: StringConstructor;
262
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
263
263
  contenteditable: {
264
264
  type: BooleanConstructor;
265
265
  default: undefined;
@@ -307,7 +307,7 @@ export declare const Base: import("kdu").DefineComponent<{
307
307
  type: BooleanConstructor;
308
308
  default: undefined;
309
309
  };
310
- class: StringConstructor;
310
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
311
311
  contenteditable: {
312
312
  type: BooleanConstructor;
313
313
  default: undefined;
@@ -373,7 +373,7 @@ export declare const Meta: import("kdu").DefineComponent<{
373
373
  type: BooleanConstructor;
374
374
  default: undefined;
375
375
  };
376
- class: StringConstructor;
376
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
377
377
  contenteditable: {
378
378
  type: BooleanConstructor;
379
379
  default: undefined;
@@ -425,7 +425,7 @@ export declare const Meta: import("kdu").DefineComponent<{
425
425
  type: BooleanConstructor;
426
426
  default: undefined;
427
427
  };
428
- class: StringConstructor;
428
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
429
429
  contenteditable: {
430
430
  type: BooleanConstructor;
431
431
  default: undefined;
@@ -488,7 +488,7 @@ export declare const Style: import("kdu").DefineComponent<{
488
488
  type: BooleanConstructor;
489
489
  default: undefined;
490
490
  };
491
- class: StringConstructor;
491
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
492
492
  contenteditable: {
493
493
  type: BooleanConstructor;
494
494
  default: undefined;
@@ -543,7 +543,7 @@ export declare const Style: import("kdu").DefineComponent<{
543
543
  type: BooleanConstructor;
544
544
  default: undefined;
545
545
  };
546
- class: StringConstructor;
546
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
547
547
  contenteditable: {
548
548
  type: BooleanConstructor;
549
549
  default: undefined;
@@ -601,7 +601,7 @@ export declare const Html: import("kdu").DefineComponent<{
601
601
  type: BooleanConstructor;
602
602
  default: undefined;
603
603
  };
604
- class: StringConstructor;
604
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
605
605
  contenteditable: {
606
606
  type: BooleanConstructor;
607
607
  default: undefined;
@@ -651,7 +651,7 @@ export declare const Html: import("kdu").DefineComponent<{
651
651
  type: BooleanConstructor;
652
652
  default: undefined;
653
653
  };
654
- class: StringConstructor;
654
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
655
655
  contenteditable: {
656
656
  type: BooleanConstructor;
657
657
  default: undefined;
@@ -703,7 +703,7 @@ export declare const Body: import("kdu").DefineComponent<{
703
703
  type: BooleanConstructor;
704
704
  default: undefined;
705
705
  };
706
- class: StringConstructor;
706
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
707
707
  contenteditable: {
708
708
  type: BooleanConstructor;
709
709
  default: undefined;
@@ -750,7 +750,7 @@ export declare const Body: import("kdu").DefineComponent<{
750
750
  type: BooleanConstructor;
751
751
  default: undefined;
752
752
  };
753
- class: StringConstructor;
753
+ class: (StringConstructor | ObjectConstructor | ArrayConstructor)[];
754
754
  contenteditable: {
755
755
  type: BooleanConstructor;
756
756
  default: undefined;