@ecopages/ecopages-jsx 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.
Files changed (31) hide show
  1. package/README.md +69 -23
  2. package/package.json +24 -6
  3. package/src/dev-transform/ecopages-jsx-dev-transform-contributor.d.ts +17 -0
  4. package/src/dev-transform/ecopages-jsx-dev-transform-contributor.js +21 -0
  5. package/src/eco-embed.d.ts +11 -0
  6. package/src/eco-embed.js +9 -0
  7. package/src/ecopages-jsx-hmr-ownership.d.ts +43 -0
  8. package/src/ecopages-jsx-hmr-ownership.js +91 -0
  9. package/src/ecopages-jsx-hmr-strategy.d.ts +19 -0
  10. package/src/ecopages-jsx-hmr-strategy.js +67 -0
  11. package/src/ecopages-jsx-mdx.d.ts +32 -0
  12. package/src/ecopages-jsx-mdx.js +48 -0
  13. package/src/ecopages-jsx-radiant-ssr-policy.d.ts +39 -0
  14. package/src/ecopages-jsx-radiant-ssr-policy.js +122 -0
  15. package/src/ecopages-jsx-renderer.d.ts +10 -52
  16. package/src/ecopages-jsx-renderer.js +143 -251
  17. package/src/ecopages-jsx.constants.d.ts +7 -0
  18. package/src/ecopages-jsx.constants.js +7 -0
  19. package/src/ecopages-jsx.plugin.d.ts +21 -93
  20. package/src/ecopages-jsx.plugin.js +135 -237
  21. package/src/ecopages-jsx.types.d.ts +32 -0
  22. package/src/ecopages-jsx.types.js +1 -0
  23. package/src/page-owned-content-script-grouping.d.ts +11 -0
  24. package/src/page-owned-content-script-grouping.js +57 -0
  25. package/src/radiant-registered-script-ssr-invalidation.d.ts +19 -0
  26. package/src/radiant-registered-script-ssr-invalidation.js +49 -0
  27. package/src/resolve-radiant-install-hydrator.d.ts +2 -0
  28. package/src/resolve-radiant-install-hydrator.js +3 -0
  29. package/CHANGELOG.md +0 -15
  30. package/src/services/jsx-runtime-bundle.service.d.ts +0 -51
  31. package/src/services/jsx-runtime-bundle.service.js +0 -206
@@ -0,0 +1,122 @@
1
+ import { createMarkupNodeLike } from '@ecopages/jsx';
2
+ import { createServerHydrationBindingState, withServerHydrationBindingState } from '@ecopages/jsx/server';
3
+ /**
4
+ * Owns optional Radiant SSR runtime installation for the JSX integration.
5
+ *
6
+ * @remarks
7
+ * Radiant's server bridge and light-DOM shim are loaded lazily because most JSX
8
+ * renders do not need them. Resolved runtime modules are cached on the policy
9
+ * instance so repeated renders on the same renderer reuse one load without
10
+ * process-wide mutable class state. Light-DOM constructor installation remains
11
+ * process-global and idempotent via {@link ensureRadiantLightDomGlobals}; the
12
+ * Radiant installer also repairs incomplete DOM globals left by another runtime.
13
+ */
14
+ export class EcopagesJsxRadiantSsrPolicy {
15
+ runtimeModules;
16
+ runtimeModulesPromise;
17
+ enabled;
18
+ constructor(enabled) {
19
+ this.enabled = enabled;
20
+ }
21
+ /**
22
+ * Ensures the Radiant SSR runtime is installed before a render needs it.
23
+ */
24
+ async prepareRuntime() {
25
+ if (!this.enabled) {
26
+ return;
27
+ }
28
+ await this.ensureRuntimeInstalled();
29
+ }
30
+ /**
31
+ * Runs one render inside Radiant's server runtime when the policy is enabled.
32
+ */
33
+ async withRuntime(render) {
34
+ if (!this.enabled) {
35
+ return render();
36
+ }
37
+ const runtimeModules = await this.runtimeModulesPromise;
38
+ if (!runtimeModules) {
39
+ return render();
40
+ }
41
+ return runtimeModules.withServerRadiantElementSsrRuntime(render);
42
+ }
43
+ /**
44
+ * Converts one Radiant custom-element instance into trusted SSR markup.
45
+ *
46
+ * @remarks
47
+ * The returned node-like wrapper lets the JSX server renderer preserve the
48
+ * generated host HTML without escaping it back into plain text.
49
+ *
50
+ * The render hook owns the host's JSX children, so `applyChildren` runs inside the host's context providers.
51
+ * A consumer passed as a child of a provider host then resolves that provider in the server HTML instead of
52
+ * rendering its fallback state.
53
+ */
54
+ renderIntrinsicElementMarkup(instance, applyChildren) {
55
+ const runtime = this.runtimeModules;
56
+ if (!runtime?.resolveRadiantElementRenderBridge(instance)) {
57
+ return undefined;
58
+ }
59
+ return createMarkupNodeLike(withServerHydrationBindingState(createServerHydrationBindingState(), () => {
60
+ const service = runtime.createRadiantElementSsrService(instance);
61
+ service.applyLightDom(() => applyChildren?.());
62
+ return service.renderHostToString({ mode: 'hydrate' }, service.getHostAttributes());
63
+ }));
64
+ }
65
+ async ensureRuntimeInstalled() {
66
+ if (!this.runtimeModulesPromise) {
67
+ const radiantLightDomShimEntry = import.meta.resolve('@ecopages/radiant/server/light-dom-shim');
68
+ const radiantElementSsrRuntimeModuleUrl = import.meta
69
+ .resolve('@ecopages/radiant/server/radiant-element-ssr');
70
+ this.runtimeModulesPromise = (async () => {
71
+ const lightDomShimModule = await import(/* @vite-ignore */ radiantLightDomShimEntry);
72
+ ensureRadiantLightDomGlobals(lightDomShimModule.ensureLightDomShim);
73
+ const radiantElementSsrRuntimeModule = (await import(
74
+ /* @vite-ignore */ radiantElementSsrRuntimeModuleUrl));
75
+ const modules = {
76
+ createRadiantElementSsrService: radiantElementSsrRuntimeModule.createRadiantElementSsrService,
77
+ ensureLightDomShim: lightDomShimModule.ensureLightDomShim,
78
+ resolveRadiantElementRenderBridge: radiantElementSsrRuntimeModule.resolveRadiantElementRenderBridge,
79
+ withServerRadiantElementSsrRuntime: radiantElementSsrRuntimeModule.withServerRadiantElementSsrRuntime,
80
+ };
81
+ this.runtimeModules = modules;
82
+ return modules;
83
+ })();
84
+ }
85
+ await this.runtimeModulesPromise;
86
+ const lightDomShimModule = this.runtimeModules;
87
+ if (lightDomShimModule) {
88
+ ensureRadiantLightDomGlobals(lightDomShimModule.ensureLightDomShim);
89
+ }
90
+ }
91
+ }
92
+ /**
93
+ * Installs Radiant light-DOM constructors on `globalThis` once.
94
+ *
95
+ * @remarks
96
+ * Custom-element constructors must be process-global; undoing them between tests
97
+ * is unsafe. Callers that clear globals for isolation must rely on a fresh policy
98
+ * instance so {@link EcopagesJsxRadiantSsrPolicy.prepareRuntime} re-runs the
99
+ * completeness check.
100
+ */
101
+ function ensureRadiantLightDomGlobals(ensureLightDomShim) {
102
+ const window = ensureLightDomShim();
103
+ if (!window) {
104
+ return;
105
+ }
106
+ Object.assign(globalThis, {
107
+ CSS: window.CSS,
108
+ CustomEvent: window.CustomEvent,
109
+ Document: window.Document,
110
+ Element: window.Element,
111
+ Event: window.Event,
112
+ EventTarget: window.EventTarget,
113
+ HTMLScriptElement: window.HTMLScriptElement,
114
+ HTMLElement: window.HTMLElement,
115
+ Node: window.Node,
116
+ document: window.document,
117
+ customElements: window.customElements,
118
+ requestAnimationFrame: window.requestAnimationFrame,
119
+ cancelAnimationFrame: window.cancelAnimationFrame,
120
+ window,
121
+ });
122
+ }
@@ -1,54 +1,12 @@
1
- import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoComponentConfig, EcoPageFile, GetMetadata, IntegrationRendererRenderOptions, RouteRendererBody } from '@ecopages/core';
2
- import type { EcoPagesAppConfig } from '@ecopages/core/internal-types';
3
- import { IntegrationRenderer, type RenderToResponseContext } from '@ecopages/core/route-renderer/integration-renderer';
4
- import type { AssetProcessingService, ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
1
+ import { createIntegrationRenderer, type IntegrationRendererHookFactory } from '@ecopages/core/integration';
5
2
  import { type JsxRenderable } from '@ecopages/jsx';
6
- type MdxPageModule = EcoPageFile<{
7
- config?: EcoComponentConfig;
8
- layout?: EcoComponent;
9
- getMetadata?: GetMetadata;
10
- }>;
11
- /**
12
- * Local Ecopages renderer for JSX templates in the docs app.
13
- *
14
- * This keeps the integration scoped to the docs package while supporting
15
- * async page, layout, and html template components on the server.
16
- */
17
- export declare class EcopagesJsxRenderer extends IntegrationRenderer<JsxRenderable> {
3
+ import type { EcopagesJsxRendererConfig } from './ecopages-jsx.types.js';
4
+ export type { EcopagesJsxRendererConfig } from './ecopages-jsx.types.js';
5
+ export type EcopagesJsxRenderer = InstanceType<ReturnType<typeof createEcopagesJsxRenderer>>;
6
+ /** Creates the hook-based Ecopages JSX renderer for one plugin configuration. */
7
+ export declare function createEcopagesJsxRenderer(jsxConfig?: EcopagesJsxRendererConfig): ReturnType<typeof createIntegrationRenderer<JsxRenderable>>;
8
+ /** Creates JSX renderer hooks that can also configure a custom renderer subclass. */
9
+ export declare function createEcopagesJsxRendererHookFactory(jsxConfig?: EcopagesJsxRendererConfig): IntegrationRendererHookFactory;
10
+ export declare const EcopagesJsxRenderer: new (options: import("@ecopages/core/integration").IntegrationRendererOptions) => import("@ecopages/core/integration").IntegrationRenderer<JsxRenderable> & {
18
11
  name: string;
19
- static mdxExtensions: string[];
20
- private intrinsicCustomElementAssets;
21
- private collectedAssetFrames;
22
- private radiantSsrEnabled;
23
- private resolveQueuedBoundaryChildren;
24
- private resolveQueuedBoundaryHtml;
25
- constructor({ appConfig, assetProcessingService, resolvedIntegrationDependencies, runtimeOrigin, }: {
26
- appConfig: EcoPagesAppConfig;
27
- assetProcessingService: AssetProcessingService;
28
- resolvedIntegrationDependencies: ProcessedAsset[];
29
- runtimeOrigin: string;
30
- });
31
- /** Returns whether the requested page file should be treated as MDX. */
32
- isMdxFile(filePath: string): boolean;
33
- /**
34
- * Supplies the intrinsic custom-element assets discovered by the plugin so
35
- * component renders can attach the correct client scripts.
36
- */
37
- setIntrinsicCustomElementAssets(assetsByTagName: Map<string, readonly ProcessedAsset[]>): void;
38
- /** Enables the Radiant SSR light-DOM shim for this renderer instance. */
39
- setRadiantSsrEnabled(enabled: boolean): void;
40
- protected importPageFile(file: string): Promise<MdxPageModule>;
41
- render(options: IntegrationRendererRenderOptions<JsxRenderable>): Promise<RouteRendererBody>;
42
- renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult>;
43
- protected createComponentBoundaryRuntime(options: {
44
- boundaryInput: ComponentRenderInput;
45
- rendererCache: Map<string, IntegrationRenderer<any>>;
46
- }): import("@ecopages/core").ComponentBoundaryRuntime;
47
- renderToResponse<P = any>(view: EcoComponent<P>, props: P, ctx: RenderToResponseContext): Promise<Response>;
48
- private beginCollectedAssetFrame;
49
- private endCollectedAssetFrame;
50
- private renderJsx;
51
- private renderEcoComponent;
52
- private createIntrinsicCustomElementRenderHook;
53
- }
54
- export {};
12
+ };
@@ -1,263 +1,155 @@
1
- import { rapidhash } from "@ecopages/core/hash";
2
- import { IntegrationRenderer } from "@ecopages/core/route-renderer/integration-renderer";
3
- import { createMarkupNodeLike } from "@ecopages/jsx";
4
- import { renderToString, withServerCustomElementRenderHook } from "@ecopages/jsx/server";
5
- import { ECOPAGES_JSX_PLUGIN_NAME } from "./ecopages-jsx.plugin.js";
6
- let radiantLightDomShimInstallPromise;
7
- async function ensureRadiantLightDomShimInstalled() {
8
- if (!radiantLightDomShimInstallPromise) {
9
- radiantLightDomShimInstallPromise = import("@ecopages/radiant/server/light-dom-shim").then((module) => {
10
- module.installLightDomShim();
11
- }).then(() => void 0);
12
- }
13
- await radiantLightDomShimInstallPromise;
1
+ import { HttpError } from '@ecopages/core/errors';
2
+ import { createIntegrationRenderer, } from '@ecopages/core/integration';
3
+ import { resolveQueuedTokens } from '@ecopages/core/integration';
4
+ import { createMarkupNodeLike } from '@ecopages/jsx';
5
+ import { renderToString, withServerCustomElementRenderHook } from '@ecopages/jsx/server';
6
+ import { isMdxFile, normalizeMdxPageModule, } from './ecopages-jsx-mdx.js';
7
+ import { recordEjsxHmrOwnership, withEjsxHmrOwnershipScope } from './ecopages-jsx-hmr-ownership.js';
8
+ import { EcopagesJsxRadiantSsrPolicy } from './ecopages-jsx-radiant-ssr-policy.js';
9
+ import { CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES, CustomElementScriptPreloader } from '@ecopages/core/integration';
10
+ import { requireAppRuntime } from '@ecopages/core/integration';
11
+ import { createSharedServerModuleImporter } from '@ecopages/core/integration';
12
+ import { ECOPAGES_JSX_PLUGIN_NAME } from './ecopages-jsx.constants.js';
13
+ /** Creates the hook-based Ecopages JSX renderer for one plugin configuration. */
14
+ export function createEcopagesJsxRenderer(jsxConfig = {}) {
15
+ return createIntegrationRenderer(ECOPAGES_JSX_PLUGIN_NAME, createEcopagesJsxRendererHookFactory(jsxConfig));
14
16
  }
15
- const isEcoFunctionComponent = (component) => {
16
- return typeof component === "function";
17
- };
18
- const renderComponent = async (component, props) => {
19
- return await component(props);
20
- };
21
- const createEcoMeta = (file) => ({
22
- id: String(rapidhash(file)),
23
- file,
24
- integration: ECOPAGES_JSX_PLUGIN_NAME
25
- });
26
- const wrapMdxPage = (page, {
27
- config,
28
- metadata
29
- }) => {
30
- const wrappedPage = (async (props) => renderComponent(page, props));
31
- wrappedPage.config = config;
32
- if (metadata) {
33
- wrappedPage.metadata = metadata;
34
- }
35
- return wrappedPage;
36
- };
37
- class EcopagesJsxRenderer extends IntegrationRenderer {
38
- name = ECOPAGES_JSX_PLUGIN_NAME;
39
- static mdxExtensions = [".mdx"];
40
- intrinsicCustomElementAssets = /* @__PURE__ */ new Map();
41
- collectedAssetFrames = [];
42
- radiantSsrEnabled = false;
43
- async resolveQueuedBoundaryChildren(children, queuedResolutionsByToken, resolveToken) {
44
- if (children === void 0) {
45
- return { assets: [] };
46
- }
47
- let assets = [];
48
- let html;
49
- if (typeof children === "string") {
50
- html = children;
51
- } else {
52
- const renderedChildren = await this.renderJsx(children);
53
- html = renderedChildren.html;
54
- assets = renderedChildren.assets;
55
- }
56
- html = await this.resolveQueuedBoundaryTokens(html, queuedResolutionsByToken, resolveToken);
57
- return {
58
- assets,
59
- html
17
+ /** Creates JSX renderer hooks that can also configure a custom renderer subclass. */
18
+ export function createEcopagesJsxRendererHookFactory(jsxConfig = {}) {
19
+ const mdxExtensions = jsxConfig.mdxExtensions ?? ['.mdx'];
20
+ return ({ appConfig, resolveComponentDependencies, resolveDependencyPath, }) => {
21
+ const radiantSsrPolicy = new EcopagesJsxRadiantSsrPolicy(jsxConfig.radiantSsrEnabled ?? false);
22
+ const ssrScriptPreloader = new CustomElementScriptPreloader({
23
+ cacheScope: CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES.ecopagesJsx,
24
+ preloadScopes: requireAppRuntime(appConfig).customElementSsrPreloadScopes,
25
+ collectForIntegration: ECOPAGES_JSX_PLUGIN_NAME,
26
+ enabled: jsxConfig.radiantSsrEnabled ?? false,
27
+ logLabel: 'ecopages-jsx',
28
+ resolveDependencyPath,
29
+ importServerModule: createSharedServerModuleImporter(appConfig),
30
+ });
31
+ const withCustomElementRenderHook = async (render) => await radiantSsrPolicy.withRuntime(() => withServerCustomElementRenderHook(({ instance, applyChildren, }) => instance ? radiantSsrPolicy.renderIntrinsicElementMarkup(instance, applyChildren) : undefined, render, { ownsChildren: true }));
32
+ const renderJsx = async (value) => await withCustomElementRenderHook(() => renderToString(value));
33
+ const normalizeForeignChildProps = (input) => {
34
+ if (!input.targetIntegration || input.targetIntegration === ECOPAGES_JSX_PLUGIN_NAME) {
35
+ return input.props;
36
+ }
37
+ return normalizeForeignChildren(input.props);
38
+ };
39
+ return {
40
+ beforeImportPageModule: async () => await radiantSsrPolicy.prepareRuntime(),
41
+ normalizePageModule: (file, module) => isMdxFile(file, mdxExtensions)
42
+ ? normalizeMdxPageModule(file, module)
43
+ : module,
44
+ beforeRender: async (options) => {
45
+ await radiantSsrPolicy.prepareRuntime();
46
+ await ssrScriptPreloader.preloadSsrScripts([
47
+ options.Page,
48
+ options.Layout,
49
+ options.HtmlTemplate,
50
+ ...(options.pageDependencyComponents ?? []),
51
+ ]);
52
+ },
53
+ renderComponent: async (input) => {
54
+ await radiantSsrPolicy.prepareRuntime();
55
+ await ssrScriptPreloader.preloadSsrScripts([input.component]);
56
+ return await withEjsxHmrOwnershipScope(async () => {
57
+ try {
58
+ if (typeof input.component !== 'function') {
59
+ throw new TypeError('JSX renderer expected a callable component.');
60
+ }
61
+ const component = input.component;
62
+ const componentProps = input.children === undefined
63
+ ? input.props
64
+ : {
65
+ ...input.props,
66
+ children: typeof input.children === 'string'
67
+ ? createMarkupNodeLike(input.children)
68
+ : input.children,
69
+ };
70
+ const content = await withCustomElementRenderHook(() => component(componentProps));
71
+ const html = await renderJsx(content);
72
+ const assets = input.component.config?.dependencies
73
+ ? await resolveComponentDependencies([input.component])
74
+ : undefined;
75
+ recordEjsxHmrOwnership([input.component]);
76
+ return {
77
+ html,
78
+ canAttachAttributes: true,
79
+ integrationName: ECOPAGES_JSX_PLUGIN_NAME,
80
+ assets,
81
+ };
82
+ }
83
+ catch (error) {
84
+ throw createRenderError('Error rendering component', error);
85
+ }
86
+ });
87
+ },
88
+ renderQueuedChildren: async (children, _runtimeContext, queuedResolutionsByToken, resolveToken) => {
89
+ if (children === undefined) {
90
+ return {};
91
+ }
92
+ const html = typeof children === 'string' ? children : await renderJsx(children);
93
+ return { html: await resolveQueuedTokens(html, queuedResolutionsByToken, resolveToken) };
94
+ },
95
+ normalizeForeignChildProps: (input) => normalizeForeignChildProps(input),
96
+ renderRoute: async (options, render) => await withEjsxHmrOwnershipScope(async () => {
97
+ const body = await render();
98
+ recordEjsxHmrOwnership([
99
+ options.Page,
100
+ options.Layout,
101
+ options.HtmlTemplate,
102
+ ...(options.pageDependencyComponents ?? []),
103
+ ]);
104
+ return body;
105
+ }),
106
+ };
60
107
  };
61
- }
62
- async resolveQueuedBoundaryHtml(html, runtimeContext) {
63
- return this.resolveRendererOwnedQueuedBoundaryHtml({
64
- html,
65
- runtimeContext,
66
- queueLabel: "Ecopages JSX",
67
- renderQueuedChildren: async (children, _runtimeContext, queuedResolutionsByToken, resolveToken) => this.resolveQueuedBoundaryChildren(children, queuedResolutionsByToken, resolveToken)
68
- });
69
- }
70
- constructor({
71
- appConfig,
72
- assetProcessingService,
73
- resolvedIntegrationDependencies,
74
- runtimeOrigin
75
- }) {
76
- super({
77
- appConfig,
78
- assetProcessingService,
79
- resolvedIntegrationDependencies,
80
- runtimeOrigin
81
- });
82
- }
83
- /** Returns whether the requested page file should be treated as MDX. */
84
- isMdxFile(filePath) {
85
- return EcopagesJsxRenderer.mdxExtensions.some((ext) => filePath.endsWith(ext));
86
- }
87
- /**
88
- * Supplies the intrinsic custom-element assets discovered by the plugin so
89
- * component renders can attach the correct client scripts.
90
- */
91
- setIntrinsicCustomElementAssets(assetsByTagName) {
92
- this.intrinsicCustomElementAssets = assetsByTagName;
93
- }
94
- /** Enables the Radiant SSR light-DOM shim for this renderer instance. */
95
- setRadiantSsrEnabled(enabled) {
96
- this.radiantSsrEnabled = enabled;
97
- }
98
- async importPageFile(file) {
99
- const module = await super.importPageFile(file);
100
- if (!this.isMdxFile(file)) {
101
- return module;
108
+ }
109
+ export const EcopagesJsxRenderer = createEcopagesJsxRenderer();
110
+ function normalizeForeignChildren(props) {
111
+ if (!('children' in props)) {
112
+ return props;
102
113
  }
103
- const Page = module.default;
104
- const normalizedConfig = {
105
- ...module.config ?? Page.config ?? {},
106
- ...module.layout ? { layout: module.layout } : {},
107
- __eco: module.config?.__eco ?? Page.config?.__eco ?? createEcoMeta(file)
108
- };
109
- const wrappedPage = wrapMdxPage(Page, {
110
- config: normalizedConfig,
111
- metadata: module.getMetadata ?? Page.metadata
112
- });
113
- return {
114
- ...module,
115
- default: wrappedPage,
116
- config: normalizedConfig
117
- };
118
- }
119
- async render(options) {
120
- try {
121
- return await this.renderPageWithDocumentShell({
122
- page: {
123
- component: options.Page,
124
- props: {
125
- ...options.pageProps,
126
- locals: options.pageLocals
127
- }
128
- },
129
- layout: options.Layout ? {
130
- component: options.Layout,
131
- props: {
132
- ...options.pageProps,
133
- locals: options.locals
134
- }
135
- } : void 0,
136
- htmlTemplate: options.HtmlTemplate,
137
- metadata: options.metadata,
138
- pageProps: options.pageProps ?? {}
139
- });
140
- } catch (error) {
141
- throw this.createRenderError("Error rendering page", error);
114
+ const children = props.children;
115
+ if (children === undefined || typeof children === 'string') {
116
+ return props;
142
117
  }
143
- }
144
- async renderComponent(input) {
145
- const assetFrame = this.beginCollectedAssetFrame();
146
- try {
147
- if (!isEcoFunctionComponent(input.component)) {
148
- throw new TypeError("JSX renderer expected a callable component.");
149
- }
150
- let props = input.props;
151
- if (input.children !== void 0) {
152
- props = {
153
- ...input.props,
154
- children: typeof input.children === "string" ? createMarkupNodeLike(input.children) : input.children
118
+ if (typeof children === 'object' &&
119
+ children !== null &&
120
+ 'nodeType' in children &&
121
+ typeof children.nodeType === 'number' &&
122
+ 'outerHTML' in children &&
123
+ typeof children.outerHTML === 'string') {
124
+ return {
125
+ ...props,
126
+ children: children.outerHTML,
155
127
  };
156
- }
157
- const content = await this.renderEcoComponent(
158
- input.component,
159
- props
160
- );
161
- const rendered = await this.renderJsx(content);
162
- const queuedBoundaryResolution = await this.resolveQueuedBoundaryHtml(
163
- rendered.html,
164
- this.getQueuedBoundaryRuntime(input)
165
- );
166
- const componentAssets = input.component.config?.dependencies && typeof this.assetProcessingService?.processDependencies === "function" ? await this.processComponentDependencies([input.component]) : [];
167
- const assets = this.htmlTransformer.dedupeProcessedAssets([
168
- ...this.endCollectedAssetFrame(assetFrame),
169
- ...queuedBoundaryResolution.assets,
170
- ...componentAssets
171
- ]);
172
- return {
173
- html: queuedBoundaryResolution.html,
174
- canAttachAttributes: true,
175
- rootTag: this.getRootTagName(queuedBoundaryResolution.html),
176
- integrationName: this.name,
177
- assets
178
- };
179
- } catch (error) {
180
- this.endCollectedAssetFrame(assetFrame);
181
- throw this.createRenderError("Error rendering component", error);
182
128
  }
183
- }
184
- createComponentBoundaryRuntime(options) {
185
- return this.createQueuedBoundaryRuntime({
186
- boundaryInput: options.boundaryInput,
187
- rendererCache: options.rendererCache
188
- });
189
- }
190
- async renderToResponse(view, props, ctx) {
191
- try {
192
- if (!isEcoFunctionComponent(view)) {
193
- throw new TypeError("JSX renderer expected a callable view component.");
194
- }
195
- return await this.renderViewWithDocumentShell({
196
- view,
197
- props,
198
- ctx,
199
- layout: view.config?.layout
200
- });
201
- } catch (error) {
202
- throw this.createRenderError("Error rendering view", error);
203
- }
204
- }
205
- beginCollectedAssetFrame() {
206
- const frame = [];
207
- this.collectedAssetFrames.push(frame);
208
- return frame;
209
- }
210
- endCollectedAssetFrame(frame) {
211
- const activeFrame = this.collectedAssetFrames.pop();
212
- if (!activeFrame || activeFrame !== frame) {
213
- return this.htmlTransformer.dedupeProcessedAssets(frame);
129
+ if (isJsxSerializableForeignChild(children)) {
130
+ return {
131
+ ...props,
132
+ children: renderToString(children),
133
+ };
214
134
  }
215
- return this.htmlTransformer.dedupeProcessedAssets(activeFrame);
216
- }
217
- async renderJsx(value) {
218
- if (this.radiantSsrEnabled) {
219
- await ensureRadiantLightDomShimInstalled();
135
+ return props;
136
+ }
137
+ function isJsxSerializableForeignChild(children) {
138
+ if (Array.isArray(children)) {
139
+ return true;
220
140
  }
221
- const collectedAssets = [];
222
- const html = withServerCustomElementRenderHook(
223
- this.createIntrinsicCustomElementRenderHook(collectedAssets),
224
- () => renderToString(value)
225
- );
226
- const dedupedAssets = this.htmlTransformer.dedupeProcessedAssets(collectedAssets);
227
- const activeFrame = this.collectedAssetFrames[this.collectedAssetFrames.length - 1];
228
- if (activeFrame) {
229
- activeFrame.push(...dedupedAssets);
141
+ if (children === null || typeof children !== 'object') {
142
+ return false;
230
143
  }
231
- return {
232
- assets: dedupedAssets,
233
- html
234
- };
235
- }
236
- async renderEcoComponent(component, props) {
237
- if (this.radiantSsrEnabled) {
238
- await ensureRadiantLightDomShimInstalled();
144
+ if ('$$typeof' in children) {
145
+ return true;
239
146
  }
240
- const collectedAssets = [];
241
- const rendered = await withServerCustomElementRenderHook(
242
- this.createIntrinsicCustomElementRenderHook(collectedAssets),
243
- () => renderComponent(component, props)
244
- );
245
- const activeFrame = this.collectedAssetFrames[this.collectedAssetFrames.length - 1];
246
- if (activeFrame) {
247
- activeFrame.push(...this.htmlTransformer.dedupeProcessedAssets(collectedAssets));
147
+ return 'strings' in children && Array.isArray(children.strings);
148
+ }
149
+ function createRenderError(message, cause) {
150
+ if (HttpError.isHttpError(cause)) {
151
+ return cause;
248
152
  }
249
- return rendered;
250
- }
251
- createIntrinsicCustomElementRenderHook(target) {
252
- return ({ tagName }) => {
253
- const assets = this.intrinsicCustomElementAssets.get(tagName);
254
- if (assets) {
255
- target.push(...assets);
256
- }
257
- return void 0;
258
- };
259
- }
153
+ const errorMessage = cause instanceof Error ? `${message}: ${cause.message}` : message;
154
+ return HttpError.InternalServerError(errorMessage);
260
155
  }
261
- export {
262
- EcopagesJsxRenderer
263
- };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Stable integration name shared by the JSX plugin and renderer.
3
+ *
4
+ * Ecopages uses this identifier to match route files, renderer instances, and
5
+ * cross-integration component boundaries.
6
+ */
7
+ export declare const ECOPAGES_JSX_PLUGIN_NAME = "ecopages-jsx";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Stable integration name shared by the JSX plugin and renderer.
3
+ *
4
+ * Ecopages uses this identifier to match route files, renderer instances, and
5
+ * cross-integration component boundaries.
6
+ */
7
+ export const ECOPAGES_JSX_PLUGIN_NAME = 'ecopages-jsx';