@ecopages/ecopages-jsx 0.2.0-beta.25 → 0.2.0-beta.27

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 CHANGED
@@ -99,6 +99,7 @@ Ecopages JSX can own the outer page shell or just a nested foreign subtree. When
99
99
 
100
100
  Important:
101
101
 
102
+ - Cross-integration JSX children must use `EcoEmbed`. Opaque plain objects fail fast at core foreign-child queue boundaries; they are not coerced with `String(object)`.
102
103
  - Components that may render foreign children must declare those children in `config.dependencies.components`.
103
104
  - Ecopages validates mixed-renderer ownership from declared dependencies during render preparation. It does not treat rendered HTML alone as the source of truth.
104
105
  - Ecopages JSX keeps raw-markup preservation and asset collection inside the JSX renderer.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/ecopages-jsx",
3
- "version": "0.2.0-beta.25",
3
+ "version": "0.2.0-beta.27",
4
4
  "description": "JSX integration plugin for Ecopages",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -31,12 +31,12 @@
31
31
  "directory": "packages/integrations/ecopages-jsx"
32
32
  },
33
33
  "dependencies": {
34
- "@ecopages/mdx": "0.2.0-beta.25",
34
+ "@ecopages/mdx": "0.2.0-beta.27",
35
35
  "@mdx-js/mdx": "^3.1.1",
36
36
  "vfile": "^6.0.3"
37
37
  },
38
38
  "peerDependencies": {
39
- "@ecopages/core": "0.2.0-beta.25",
39
+ "@ecopages/core": "0.2.0-beta.27",
40
40
  "@ecopages/jsx": "0.3.0-beta.2",
41
41
  "@ecopages/radiant": "0.3.0-beta.2"
42
42
  }
@@ -4,13 +4,14 @@ import { createMarkupNodeLike } from '@ecopages/jsx';
4
4
  *
5
5
  * @remarks
6
6
  * Radiant's server bridge and light-DOM shim are loaded lazily because most JSX
7
- * renders do not need them. Once enabled, the resolved runtime modules are
8
- * cached statically so repeated page and component renders do not keep paying
9
- * module-resolution or shim-installation costs.
7
+ * renders do not need them. Resolved runtime modules are cached on the policy
8
+ * instance so repeated renders on the same renderer reuse one load without
9
+ * process-wide mutable class state. Light-DOM constructor installation remains
10
+ * process-global and idempotent via {@link ensureRadiantLightDomGlobals}.
10
11
  */
11
12
  export declare class EcopagesJsxRadiantSsrPolicy {
12
- private static runtimeModules;
13
- private static runtimeModulesPromise;
13
+ private runtimeModules;
14
+ private runtimeModulesPromise;
14
15
  private readonly enabled;
15
16
  constructor(enabled: boolean);
16
17
  /**
@@ -1,8 +1,8 @@
1
1
  import { createMarkupNodeLike } from "@ecopages/jsx";
2
2
  import { createServerHydrationBindingState, withServerHydrationBindingState } from "@ecopages/jsx/server";
3
3
  class EcopagesJsxRadiantSsrPolicy {
4
- static runtimeModules;
5
- static runtimeModulesPromise;
4
+ runtimeModules;
5
+ runtimeModulesPromise;
6
6
  enabled;
7
7
  constructor(enabled) {
8
8
  this.enabled = enabled;
@@ -23,7 +23,7 @@ class EcopagesJsxRadiantSsrPolicy {
23
23
  if (!this.enabled) {
24
24
  return render();
25
25
  }
26
- const runtimeModules = await EcopagesJsxRadiantSsrPolicy.runtimeModulesPromise;
26
+ const runtimeModules = await this.runtimeModulesPromise;
27
27
  if (!runtimeModules) {
28
28
  return render();
29
29
  }
@@ -37,7 +37,7 @@ class EcopagesJsxRadiantSsrPolicy {
37
37
  * generated host HTML without escaping it back into plain text.
38
38
  */
39
39
  renderIntrinsicElementMarkup(instance) {
40
- const renderBridge = EcopagesJsxRadiantSsrPolicy.runtimeModules?.resolveRadiantElementRenderBridge(instance);
40
+ const renderBridge = this.runtimeModules?.resolveRadiantElementRenderBridge(instance);
41
41
  if (!renderBridge) {
42
42
  return void 0;
43
43
  }
@@ -51,13 +51,13 @@ class EcopagesJsxRadiantSsrPolicy {
51
51
  );
52
52
  }
53
53
  async ensureRuntimeInstalled() {
54
- if (!EcopagesJsxRadiantSsrPolicy.runtimeModulesPromise) {
54
+ if (!this.runtimeModulesPromise) {
55
55
  const radiantLightDomShimEntry = import.meta.resolve("@ecopages/radiant/server/light-dom-shim");
56
56
  const radiantElementSsrRuntimeModuleUrl = new URL(
57
57
  "./radiant-element-ssr-bridge.js",
58
58
  radiantLightDomShimEntry
59
59
  ).href;
60
- EcopagesJsxRadiantSsrPolicy.runtimeModulesPromise = (async () => {
60
+ this.runtimeModulesPromise = (async () => {
61
61
  const lightDomShimModule2 = await import(radiantLightDomShimEntry);
62
62
  ensureRadiantLightDomGlobals(lightDomShimModule2.installLightDomShim);
63
63
  const radiantElementSsrRuntimeModule = await import(radiantElementSsrRuntimeModuleUrl);
@@ -66,12 +66,12 @@ class EcopagesJsxRadiantSsrPolicy {
66
66
  resolveRadiantElementRenderBridge: radiantElementSsrRuntimeModule.resolveRadiantElementRenderBridge,
67
67
  withServerRadiantElementSsrRuntime: radiantElementSsrRuntimeModule.withServerRadiantElementSsrRuntime
68
68
  };
69
- EcopagesJsxRadiantSsrPolicy.runtimeModules = modules;
69
+ this.runtimeModules = modules;
70
70
  return modules;
71
71
  })();
72
72
  }
73
- await EcopagesJsxRadiantSsrPolicy.runtimeModulesPromise;
74
- const lightDomShimModule = EcopagesJsxRadiantSsrPolicy.runtimeModules;
73
+ await this.runtimeModulesPromise;
74
+ const lightDomShimModule = this.runtimeModules;
75
75
  if (lightDomShimModule) {
76
76
  ensureRadiantLightDomGlobals(lightDomShimModule.installLightDomShim);
77
77
  }
@@ -24,13 +24,6 @@ export declare class EcopagesJsxRenderSession {
24
24
  * JSX SSR scope bridge.
25
25
  */
26
26
  withActiveScope<T>(render: () => T): T;
27
- /**
28
- * Runs work with the current page-level hydrate binding namespace attached to JSX SSR.
29
- *
30
- * This lets page, layout, and document renders participate in one client-owned
31
- * hydration root even though Ecopages composes them through multiple sibling render calls.
32
- */
33
- withHydrationBindingScope<T>(render: () => T): T;
34
27
  beginCollectedAssetFrame(): ProcessedAsset[];
35
28
  endCollectedAssetFrame(frame: ProcessedAsset[]): ProcessedAsset[];
36
29
  recordCollectedAssets(collectedAssets: ProcessedAsset[]): ProcessedAsset[];
@@ -1,10 +1,5 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
- import {
3
- createServerHydrationBindingState,
4
- getActiveSsrScopeValue,
5
- withActiveSsrScopeValue,
6
- withServerHydrationBindingState
7
- } from "@ecopages/jsx/server";
2
+ import { getActiveSsrScopeValue, withActiveSsrScopeValue } from "@ecopages/jsx/server";
8
3
  const ECOPAGES_JSX_SSR_RENDER_STATE_KEY = /* @__PURE__ */ Symbol.for("@ecopages/ecopages-jsx.ssr-render-state");
9
4
  const renderStateStorage = new AsyncLocalStorage();
10
5
  class EcopagesJsxRenderSession {
@@ -32,23 +27,13 @@ class EcopagesJsxRenderSession {
32
27
  return renderStateStorage.run(jsxScopeState, () => render());
33
28
  }
34
29
  const state = {
35
- collectedAssetFrames: [],
36
- hydrationBindingState: createServerHydrationBindingState()
30
+ collectedAssetFrames: []
37
31
  };
38
32
  return renderStateStorage.run(
39
33
  state,
40
34
  () => withActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY, state, render)
41
35
  );
42
36
  }
43
- /**
44
- * Runs work with the current page-level hydrate binding namespace attached to JSX SSR.
45
- *
46
- * This lets page, layout, and document renders participate in one client-owned
47
- * hydration root even though Ecopages composes them through multiple sibling render calls.
48
- */
49
- withHydrationBindingScope(render) {
50
- return withServerHydrationBindingState(this.getState().hydrationBindingState, render);
51
- }
52
37
  beginCollectedAssetFrame() {
53
38
  const state = this.getState();
54
39
  const frame = [];
@@ -16,7 +16,21 @@ export declare class EcopagesJsxRenderer extends IntegrationRenderer<JsxRenderab
16
16
  private readonly mdxExtensions;
17
17
  private readonly renderSession;
18
18
  private readonly radiantSsrPolicy;
19
+ /**
20
+ * Serializes foreign-child props for string-first boundaries.
21
+ *
22
+ * @remarks
23
+ * DOM nodes become `outerHTML`. JSX trees, arrays, and template results are
24
+ * stringified here. Other values are left for core foreign-child interception,
25
+ * which rejects opaque plain objects before queueing. Cross-integration
26
+ * children must use `EcoEmbed`.
27
+ */
19
28
  private normalizeForeignChildProps;
29
+ /**
30
+ * Returns whether children are a JSX/template shape this renderer can
31
+ * serialize before foreign-child queueing.
32
+ */
33
+ private isJsxSerializableForeignChild;
20
34
  /**
21
35
  * Re-renders queued JSX children inside the owning renderer so nested custom
22
36
  * elements and queued foreign subtrees contribute assets to the same frame.
@@ -16,6 +16,15 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
16
16
  mdxExtensions;
17
17
  renderSession;
18
18
  radiantSsrPolicy;
19
+ /**
20
+ * Serializes foreign-child props for string-first boundaries.
21
+ *
22
+ * @remarks
23
+ * DOM nodes become `outerHTML`. JSX trees, arrays, and template results are
24
+ * stringified here. Other values are left for core foreign-child interception,
25
+ * which rejects opaque plain objects before queueing. Cross-integration
26
+ * children must use `EcoEmbed`.
27
+ */
19
28
  normalizeForeignChildProps(props) {
20
29
  if (!("children" in props)) {
21
30
  return props;
@@ -30,10 +39,29 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
30
39
  children: children.outerHTML
31
40
  };
32
41
  }
33
- return {
34
- ...props,
35
- children: renderToString(children)
36
- };
42
+ if (this.isJsxSerializableForeignChild(children)) {
43
+ return {
44
+ ...props,
45
+ children: renderToString(children)
46
+ };
47
+ }
48
+ return props;
49
+ }
50
+ /**
51
+ * Returns whether children are a JSX/template shape this renderer can
52
+ * serialize before foreign-child queueing.
53
+ */
54
+ isJsxSerializableForeignChild(children) {
55
+ if (Array.isArray(children)) {
56
+ return true;
57
+ }
58
+ if (children === null || typeof children !== "object") {
59
+ return false;
60
+ }
61
+ if ("$$typeof" in children) {
62
+ return true;
63
+ }
64
+ return "strings" in children && Array.isArray(children.strings);
37
65
  }
38
66
  /**
39
67
  * Re-renders queued JSX children inside the owning renderer so nested custom