@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.
- package/README.md +69 -23
- package/package.json +24 -6
- package/src/dev-transform/ecopages-jsx-dev-transform-contributor.d.ts +17 -0
- package/src/dev-transform/ecopages-jsx-dev-transform-contributor.js +21 -0
- package/src/eco-embed.d.ts +11 -0
- package/src/eco-embed.js +9 -0
- package/src/ecopages-jsx-hmr-ownership.d.ts +43 -0
- package/src/ecopages-jsx-hmr-ownership.js +91 -0
- package/src/ecopages-jsx-hmr-strategy.d.ts +19 -0
- package/src/ecopages-jsx-hmr-strategy.js +67 -0
- package/src/ecopages-jsx-mdx.d.ts +32 -0
- package/src/ecopages-jsx-mdx.js +48 -0
- package/src/ecopages-jsx-radiant-ssr-policy.d.ts +39 -0
- package/src/ecopages-jsx-radiant-ssr-policy.js +122 -0
- package/src/ecopages-jsx-renderer.d.ts +10 -52
- package/src/ecopages-jsx-renderer.js +143 -251
- package/src/ecopages-jsx.constants.d.ts +7 -0
- package/src/ecopages-jsx.constants.js +7 -0
- package/src/ecopages-jsx.plugin.d.ts +21 -93
- package/src/ecopages-jsx.plugin.js +135 -237
- package/src/ecopages-jsx.types.d.ts +32 -0
- package/src/ecopages-jsx.types.js +1 -0
- package/src/page-owned-content-script-grouping.d.ts +11 -0
- package/src/page-owned-content-script-grouping.js +57 -0
- package/src/radiant-registered-script-ssr-invalidation.d.ts +19 -0
- package/src/radiant-registered-script-ssr-invalidation.js +49 -0
- package/src/resolve-radiant-install-hydrator.d.ts +2 -0
- package/src/resolve-radiant-install-hydrator.js +3 -0
- package/CHANGELOG.md +0 -15
- package/src/services/jsx-runtime-bundle.service.d.ts +0 -51
- 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
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
|
|
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
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
|
|
135
|
+
return props;
|
|
136
|
+
}
|
|
137
|
+
function isJsxSerializableForeignChild(children) {
|
|
138
|
+
if (Array.isArray(children)) {
|
|
139
|
+
return true;
|
|
220
140
|
}
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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";
|