@ecopages/ecopages-jsx 0.2.0-beta.9 → 0.2.0-rc.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 +2 -0
- package/package.json +5 -4
- 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.js +8 -10
- package/src/ecopages-jsx-hmr-ownership.d.ts +45 -0
- package/src/ecopages-jsx-hmr-ownership.js +77 -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 +2 -4
- package/src/ecopages-jsx-mdx.js +63 -106
- package/src/ecopages-jsx-radiant-ssr-policy.d.ts +7 -5
- package/src/ecopages-jsx-radiant-ssr-policy.js +107 -73
- package/src/ecopages-jsx-render-session.d.ts +5 -9
- package/src/ecopages-jsx-render-session.js +104 -86
- package/src/ecopages-jsx-renderer.d.ts +22 -9
- package/src/ecopages-jsx-renderer.js +278 -234
- package/src/ecopages-jsx.constants.js +7 -4
- package/src/ecopages-jsx.plugin.d.ts +17 -0
- package/src/ecopages-jsx.plugin.js +162 -111
- package/src/ecopages-jsx.types.js +1 -0
- package/src/page-owned-content-script-grouping.d.ts +12 -0
- package/src/page-owned-content-script-grouping.js +63 -0
- package/src/radiant-registered-script-ssr-invalidation.d.ts +18 -0
- package/src/radiant-registered-script-ssr-invalidation.js +47 -0
- package/src/resolve-radiant-install-hydrator.d.ts +2 -0
- package/src/resolve-radiant-install-hydrator.js +3 -0
|
@@ -1,240 +1,284 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
} from
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
1
|
+
import { IntegrationRenderer, } from '@ecopages/core/route-renderer/orchestration/integration-renderer';
|
|
2
|
+
import { getForeignSubtreeResolutionContextKey, resolveOwningIntegrationRenderer, } from '@ecopages/core/route-renderer/orchestration/foreign-child/owning-renderer-resolution';
|
|
3
|
+
import { createMarkupNodeLike } from '@ecopages/jsx';
|
|
4
|
+
import { renderToString, withServerCustomElementRenderHook } from '@ecopages/jsx/server';
|
|
5
|
+
import { ECOPAGES_JSX_PLUGIN_NAME } from './ecopages-jsx.constants.js';
|
|
6
|
+
import { isMdxFile, normalizeMdxPageModule, } from './ecopages-jsx-mdx.js';
|
|
7
|
+
import { EcopagesJsxRenderSession } from './ecopages-jsx-render-session.js';
|
|
8
|
+
import { EcopagesJsxRadiantSsrPolicy } from './ecopages-jsx-radiant-ssr-policy.js';
|
|
9
|
+
/**
|
|
10
|
+
* Local Ecopages renderer for JSX templates in the docs app.
|
|
11
|
+
*
|
|
12
|
+
* This keeps the integration scoped to the docs package while supporting
|
|
13
|
+
* async page, layout, and html template components on the server.
|
|
14
|
+
*/
|
|
15
|
+
export class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
16
|
+
name = ECOPAGES_JSX_PLUGIN_NAME;
|
|
17
|
+
mdxExtensions;
|
|
18
|
+
renderSession;
|
|
19
|
+
radiantSsrPolicy;
|
|
20
|
+
/**
|
|
21
|
+
* Serializes foreign-child props for string-first boundaries.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* DOM nodes become `outerHTML`. JSX trees, arrays, and template results are
|
|
25
|
+
* stringified here. Other values are left for core foreign-child interception,
|
|
26
|
+
* which rejects opaque plain objects before queueing. Cross-integration
|
|
27
|
+
* children must use `EcoEmbed`.
|
|
28
|
+
*/
|
|
29
|
+
normalizeForeignChildProps(props) {
|
|
30
|
+
if (!('children' in props)) {
|
|
31
|
+
return props;
|
|
32
|
+
}
|
|
33
|
+
const children = props.children;
|
|
34
|
+
if (children === undefined || typeof children === 'string') {
|
|
35
|
+
return props;
|
|
36
|
+
}
|
|
37
|
+
if (typeof children === 'object' &&
|
|
38
|
+
children !== null &&
|
|
39
|
+
'nodeType' in children &&
|
|
40
|
+
typeof children.nodeType === 'number' &&
|
|
41
|
+
'outerHTML' in children &&
|
|
42
|
+
typeof children.outerHTML === 'string') {
|
|
43
|
+
return {
|
|
44
|
+
...props,
|
|
45
|
+
children: children.outerHTML,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (this.isJsxSerializableForeignChild(children)) {
|
|
49
|
+
return {
|
|
50
|
+
...props,
|
|
51
|
+
children: renderToString(children),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return props;
|
|
44
55
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Returns whether children are a JSX/template shape this renderer can
|
|
58
|
+
* serialize before foreign-child queueing.
|
|
59
|
+
*/
|
|
60
|
+
isJsxSerializableForeignChild(children) {
|
|
61
|
+
if (Array.isArray(children)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
if (children === null || typeof children !== 'object') {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if ('$$typeof' in children) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return 'strings' in children && Array.isArray(children.strings);
|
|
53
71
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
html
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Resolves queued foreign subtrees after JSX has been stringified.
|
|
66
|
-
*
|
|
67
|
-
* JSX content needs one extra render pass because child foreign subtrees may emit
|
|
68
|
-
* additional browser assets while also replacing placeholder tokens.
|
|
69
|
-
*/
|
|
70
|
-
async resolveOwnedForeignSubtreeHtml(html, runtimeContext) {
|
|
71
|
-
return this.foreignSubtreeExecutionService.resolveQueuedHtml({
|
|
72
|
-
currentIntegrationName: this.name,
|
|
73
|
-
html,
|
|
74
|
-
runtimeContext,
|
|
75
|
-
queueLabel: "Ecopages JSX",
|
|
76
|
-
getOwningRenderer: (integrationName, rendererCache) => this.getIntegrationRendererForName(integrationName, rendererCache),
|
|
77
|
-
applyAttributesToFirstElement: (resolvedHtml, attributes) => this.htmlTransformer.applyAttributesToFirstElement(resolvedHtml, attributes),
|
|
78
|
-
dedupeProcessedAssets: (assets) => this.htmlTransformer.dedupeProcessedAssets(assets),
|
|
79
|
-
renderQueuedChildren: async (children, _runtimeContext, queuedResolutionsByToken, resolveToken) => this.renderQueuedForeignSubtreeChildren(children, queuedResolutionsByToken, resolveToken)
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
createForeignChildRuntime(options) {
|
|
83
|
-
const runtime = super.createForeignChildRuntime(options);
|
|
84
|
-
const interceptForeignChild = runtime.interceptForeignChild;
|
|
85
|
-
const interceptForeignChildSync = runtime.interceptForeignChildSync;
|
|
86
|
-
const wrapInput = (input) => ({
|
|
87
|
-
...input,
|
|
88
|
-
props: input.targetIntegration && input.targetIntegration !== this.name ? this.normalizeForeignChildProps(input.props) : input.props
|
|
89
|
-
});
|
|
90
|
-
return {
|
|
91
|
-
interceptForeignChild: interceptForeignChild ? (input) => interceptForeignChild(wrapInput(input)) : void 0,
|
|
92
|
-
interceptForeignChildSync: interceptForeignChildSync ? (input) => interceptForeignChildSync(wrapInput(input)) : void 0
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
constructor({
|
|
96
|
-
appConfig,
|
|
97
|
-
assetProcessingService,
|
|
98
|
-
resolvedIntegrationDependencies,
|
|
99
|
-
jsxConfig,
|
|
100
|
-
runtimeOrigin
|
|
101
|
-
}) {
|
|
102
|
-
super({
|
|
103
|
-
appConfig,
|
|
104
|
-
assetProcessingService,
|
|
105
|
-
resolvedIntegrationDependencies,
|
|
106
|
-
runtimeOrigin
|
|
107
|
-
});
|
|
108
|
-
this.mdxExtensions = jsxConfig?.mdxExtensions ?? [".mdx"];
|
|
109
|
-
this.renderSession = new EcopagesJsxRenderSession(
|
|
110
|
-
(assets) => this.htmlTransformer.dedupeProcessedAssets(assets)
|
|
111
|
-
);
|
|
112
|
-
this.radiantSsrPolicy = jsxConfig?.radiantSsrPolicy ?? new EcopagesJsxRadiantSsrPolicy(jsxConfig?.radiantSsrEnabled ?? false);
|
|
113
|
-
}
|
|
114
|
-
/** Returns whether the requested page file should be treated as MDX. */
|
|
115
|
-
isMdxFile(filePath) {
|
|
116
|
-
return isMdxFile(filePath, this.mdxExtensions);
|
|
117
|
-
}
|
|
118
|
-
async importPageFile(file, options) {
|
|
119
|
-
return await this.withPreparedRadiantRuntime(async () => {
|
|
120
|
-
const module = await super.importPageFile(file, options);
|
|
121
|
-
return this.isMdxFile(file) ? normalizeMdxPageModule(file, module) : module;
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
async render(options) {
|
|
125
|
-
return await this.withPreparedRadiantRuntime(
|
|
126
|
-
() => this.renderSession.withActiveScope(async () => {
|
|
127
|
-
try {
|
|
128
|
-
return await this.renderPageWithDocumentShell({
|
|
129
|
-
page: {
|
|
130
|
-
component: options.Page,
|
|
131
|
-
props: {
|
|
132
|
-
...options.pageProps,
|
|
133
|
-
locals: options.pageLocals
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
layout: options.Layout ? {
|
|
137
|
-
component: options.Layout,
|
|
138
|
-
props: {
|
|
139
|
-
...options.pageProps,
|
|
140
|
-
locals: options.locals
|
|
141
|
-
}
|
|
142
|
-
} : void 0,
|
|
143
|
-
htmlTemplate: options.HtmlTemplate,
|
|
144
|
-
metadata: options.metadata,
|
|
145
|
-
pageProps: options.pageProps ?? {}
|
|
146
|
-
});
|
|
147
|
-
} catch (error) {
|
|
148
|
-
throw this.createRenderError("Error rendering page", error);
|
|
72
|
+
/**
|
|
73
|
+
* Re-renders queued JSX children inside the owning renderer so nested custom
|
|
74
|
+
* elements and queued foreign subtrees contribute assets to the same frame.
|
|
75
|
+
*/
|
|
76
|
+
async renderQueuedForeignSubtreeChildren(children, queuedResolutionsByToken, resolveToken) {
|
|
77
|
+
if (children === undefined) {
|
|
78
|
+
return { assets: [] };
|
|
149
79
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return await this.withPreparedRadiantRuntime(
|
|
155
|
-
() => this.renderSession.withActiveScope(async () => {
|
|
156
|
-
const assetFrame = this.renderSession.beginCollectedAssetFrame();
|
|
157
|
-
try {
|
|
158
|
-
if (typeof input.component !== "function") {
|
|
159
|
-
throw new TypeError("JSX renderer expected a callable component.");
|
|
160
|
-
}
|
|
161
|
-
const component = input.component;
|
|
162
|
-
const componentProps = input.children === void 0 ? input.props : {
|
|
163
|
-
...input.props,
|
|
164
|
-
children: typeof input.children === "string" ? createMarkupNodeLike(input.children) : input.children
|
|
165
|
-
};
|
|
166
|
-
const content = await this.withCustomElementRenderHook(() => component(componentProps));
|
|
167
|
-
const rendered = await this.renderJsx(content);
|
|
168
|
-
const queuedForeignSubtreeResolution = await this.resolveOwnedForeignSubtreeHtml(
|
|
169
|
-
rendered.html,
|
|
170
|
-
this.getQueuedForeignSubtreeResolutionContext(
|
|
171
|
-
input
|
|
172
|
-
)
|
|
173
|
-
);
|
|
174
|
-
const componentAssets = input.component.config?.dependencies && typeof this.assetProcessingService?.processDependencies === "function" ? await this.processComponentDependencies([input.component]) : [];
|
|
175
|
-
const assets = this.htmlTransformer.dedupeProcessedAssets([
|
|
176
|
-
...this.renderSession.endCollectedAssetFrame(assetFrame),
|
|
177
|
-
...queuedForeignSubtreeResolution.assets,
|
|
178
|
-
...componentAssets
|
|
179
|
-
]);
|
|
180
|
-
return {
|
|
181
|
-
html: queuedForeignSubtreeResolution.html,
|
|
182
|
-
canAttachAttributes: true,
|
|
183
|
-
rootTag: this.getRootTagName(queuedForeignSubtreeResolution.html),
|
|
184
|
-
integrationName: this.name,
|
|
185
|
-
assets
|
|
186
|
-
};
|
|
187
|
-
} catch (error) {
|
|
188
|
-
this.renderSession.endCollectedAssetFrame(assetFrame);
|
|
189
|
-
throw this.createRenderError("Error rendering component", error);
|
|
80
|
+
let assets = [];
|
|
81
|
+
let html;
|
|
82
|
+
if (typeof children === 'string') {
|
|
83
|
+
html = children;
|
|
190
84
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return await this.withPreparedRadiantRuntime(
|
|
196
|
-
() => this.renderSession.withActiveScope(async () => {
|
|
197
|
-
try {
|
|
198
|
-
if (typeof view !== "function") {
|
|
199
|
-
throw new TypeError("JSX renderer expected a callable view component.");
|
|
200
|
-
}
|
|
201
|
-
const viewComponent = view;
|
|
202
|
-
return await this.renderViewWithDocumentShell({
|
|
203
|
-
view: viewComponent,
|
|
204
|
-
props,
|
|
205
|
-
ctx,
|
|
206
|
-
layout: viewComponent.config?.layout
|
|
207
|
-
});
|
|
208
|
-
} catch (error) {
|
|
209
|
-
throw this.createRenderError("Error rendering view", error);
|
|
85
|
+
else {
|
|
86
|
+
const renderedChildren = await this.renderJsx(children);
|
|
87
|
+
html = renderedChildren.html;
|
|
88
|
+
assets = renderedChildren.assets;
|
|
210
89
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
90
|
+
html = await this.foreignSubtreeExecutionService.resolveQueuedTokens(html, queuedResolutionsByToken, resolveToken);
|
|
91
|
+
return {
|
|
92
|
+
assets,
|
|
93
|
+
html,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
createForeignChildRuntime(options) {
|
|
97
|
+
const runtime = super.createForeignChildRuntime(options);
|
|
98
|
+
const interceptForeignChild = runtime.interceptForeignChild;
|
|
99
|
+
const interceptForeignChildSync = runtime.interceptForeignChildSync;
|
|
100
|
+
const wrapInput = (input) => ({
|
|
101
|
+
...input,
|
|
102
|
+
props: input.targetIntegration && input.targetIntegration !== this.name
|
|
103
|
+
? this.normalizeForeignChildProps(input.props)
|
|
104
|
+
: input.props,
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
interceptForeignChild: interceptForeignChild
|
|
108
|
+
? (input) => interceptForeignChild(wrapInput(input))
|
|
109
|
+
: undefined,
|
|
110
|
+
interceptForeignChildSync: interceptForeignChildSync
|
|
111
|
+
? (input) => interceptForeignChildSync(wrapInput(input))
|
|
112
|
+
: undefined,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
constructor({ appConfig, assetProcessingService, resolvedIntegrationDependencies, jsxConfig, runtimeOrigin, }) {
|
|
116
|
+
super({
|
|
117
|
+
appConfig,
|
|
118
|
+
assetProcessingService,
|
|
119
|
+
resolvedIntegrationDependencies,
|
|
120
|
+
runtimeOrigin,
|
|
121
|
+
});
|
|
122
|
+
this.mdxExtensions = jsxConfig?.mdxExtensions ?? ['.mdx'];
|
|
123
|
+
this.renderSession = new EcopagesJsxRenderSession((assets) => this.htmlTransformer.dedupeProcessedAssets(assets));
|
|
124
|
+
this.radiantSsrPolicy =
|
|
125
|
+
jsxConfig?.radiantSsrPolicy ?? new EcopagesJsxRadiantSsrPolicy(jsxConfig?.radiantSsrEnabled ?? false);
|
|
126
|
+
}
|
|
127
|
+
/** Returns whether the requested page file should be treated as MDX. */
|
|
128
|
+
isMdxFile(filePath) {
|
|
129
|
+
return isMdxFile(filePath, this.mdxExtensions);
|
|
130
|
+
}
|
|
131
|
+
async importPageFile(file, options) {
|
|
132
|
+
return await this.withPreparedRadiantRuntime(async () => {
|
|
133
|
+
const module = (await super.importPageFile(file, options));
|
|
134
|
+
return this.isMdxFile(file) ? normalizeMdxPageModule(file, module) : module;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async render(options) {
|
|
138
|
+
return await this.withPreparedRadiantRuntime(async () => await this.renderSession.withActiveScope(async () => {
|
|
139
|
+
try {
|
|
140
|
+
const result = await this.renderPageWithDocumentShell({
|
|
141
|
+
page: {
|
|
142
|
+
component: options.Page,
|
|
143
|
+
props: {
|
|
144
|
+
...options.pageProps,
|
|
145
|
+
locals: options.pageLocals,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
layout: options.Layout
|
|
149
|
+
? {
|
|
150
|
+
component: options.Layout,
|
|
151
|
+
props: {
|
|
152
|
+
...options.pageProps,
|
|
153
|
+
locals: options.locals,
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
: undefined,
|
|
157
|
+
htmlTemplate: options.HtmlTemplate,
|
|
158
|
+
metadata: options.metadata,
|
|
159
|
+
pageProps: options.pageProps ?? {},
|
|
160
|
+
});
|
|
161
|
+
this.recordHmrOwnership([
|
|
162
|
+
options.Page,
|
|
163
|
+
options.Layout,
|
|
164
|
+
options.HtmlTemplate,
|
|
165
|
+
...(options.resolvedPageDependencyComponents ?? []),
|
|
166
|
+
]);
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
throw this.createRenderError('Error rendering page', error);
|
|
171
|
+
}
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
async renderComponent(input) {
|
|
175
|
+
return await this.withPreparedRadiantRuntime(async () => await this.renderSession.withActiveScope(async () => {
|
|
176
|
+
const assetFrame = this.renderSession.beginCollectedAssetFrame();
|
|
177
|
+
try {
|
|
178
|
+
if (typeof input.component !== 'function') {
|
|
179
|
+
throw new TypeError('JSX renderer expected a callable component.');
|
|
180
|
+
}
|
|
181
|
+
const component = input.component;
|
|
182
|
+
const componentProps = input.children === undefined
|
|
183
|
+
? input.props
|
|
184
|
+
: {
|
|
185
|
+
...input.props,
|
|
186
|
+
children: typeof input.children === 'string'
|
|
187
|
+
? createMarkupNodeLike(input.children)
|
|
188
|
+
: input.children,
|
|
189
|
+
};
|
|
190
|
+
const content = await this.withCustomElementRenderHook(() => component(componentProps));
|
|
191
|
+
const rendered = await this.renderJsx(content);
|
|
192
|
+
const queuedForeignSubtreeResolution = await this.foreignSubtreeExecutionService.resolveQueuedHtml({
|
|
193
|
+
currentIntegrationName: this.name,
|
|
194
|
+
html: rendered.html,
|
|
195
|
+
runtimeContext: this.foreignSubtreeExecutionService.getQueuedRuntimeContext(input, getForeignSubtreeResolutionContextKey(this.name)),
|
|
196
|
+
queueLabel: 'Ecopages JSX',
|
|
197
|
+
getOwningRenderer: (integrationName, rendererCache) => resolveOwningIntegrationRenderer({
|
|
198
|
+
appConfig: this.appConfig,
|
|
199
|
+
runtimeOrigin: this.runtimeOrigin,
|
|
200
|
+
currentIntegrationName: this.name,
|
|
201
|
+
currentRenderer: this,
|
|
202
|
+
integrationName,
|
|
203
|
+
cache: rendererCache,
|
|
204
|
+
}),
|
|
205
|
+
applyAttributesToFirstElement: (resolvedHtml, attributes) => this.htmlTransformer.applyAttributesToFirstElement(resolvedHtml, attributes),
|
|
206
|
+
dedupeProcessedAssets: (assets) => this.htmlTransformer.dedupeProcessedAssets(assets),
|
|
207
|
+
renderQueuedChildren: (children, _runtimeContext, queuedResolutionsByToken, resolveToken) => this.renderQueuedForeignSubtreeChildren(children, queuedResolutionsByToken, resolveToken),
|
|
208
|
+
});
|
|
209
|
+
const componentAssets = input.component.config?.dependencies &&
|
|
210
|
+
typeof this.assetProcessingService?.processDependencies === 'function'
|
|
211
|
+
? await this.processComponentDependencies([input.component])
|
|
212
|
+
: [];
|
|
213
|
+
const assets = this.htmlTransformer.dedupeProcessedAssets([
|
|
214
|
+
...this.renderSession.endCollectedAssetFrame(assetFrame),
|
|
215
|
+
...queuedForeignSubtreeResolution.assets,
|
|
216
|
+
...componentAssets,
|
|
217
|
+
]);
|
|
218
|
+
this.recordHmrOwnership([input.component]);
|
|
219
|
+
return this.finalizeIslandComponentRender(input, {
|
|
220
|
+
html: queuedForeignSubtreeResolution.html,
|
|
221
|
+
canAttachAttributes: true,
|
|
222
|
+
rootTag: this.getRootTagName(queuedForeignSubtreeResolution.html),
|
|
223
|
+
integrationName: this.name,
|
|
224
|
+
assets,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
this.renderSession.endCollectedAssetFrame(assetFrame);
|
|
229
|
+
throw this.createRenderError('Error rendering component', error);
|
|
230
|
+
}
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
async renderToResponse(view, props, ctx) {
|
|
234
|
+
return await this.withPreparedRadiantRuntime(async () => await this.renderSession.withActiveScope(async () => {
|
|
235
|
+
try {
|
|
236
|
+
if (typeof view !== 'function') {
|
|
237
|
+
throw new TypeError('JSX renderer expected a callable view component.');
|
|
238
|
+
}
|
|
239
|
+
const viewComponent = view;
|
|
240
|
+
const layouts = viewComponent.config?.layouts;
|
|
241
|
+
const response = await this.renderViewWithDocumentShell({
|
|
242
|
+
view: viewComponent,
|
|
243
|
+
props: props,
|
|
244
|
+
ctx,
|
|
245
|
+
layout: layouts?.[layouts.length - 1],
|
|
246
|
+
});
|
|
247
|
+
this.recordHmrOwnership([view]);
|
|
248
|
+
return response;
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
throw this.createRenderError('Error rendering view', error);
|
|
252
|
+
}
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
async renderJsx(value) {
|
|
256
|
+
const collectedAssets = [];
|
|
257
|
+
const html = await this.withCustomElementRenderHook(() => renderToString(value));
|
|
258
|
+
const dedupedAssets = this.renderSession.recordCollectedAssets(collectedAssets);
|
|
259
|
+
return {
|
|
260
|
+
assets: dedupedAssets,
|
|
261
|
+
html,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
async withCustomElementRenderHook(render) {
|
|
265
|
+
return await this.radiantSsrPolicy.withRuntime(() => withServerCustomElementRenderHook(this.createIntrinsicCustomElementRenderHook(), render));
|
|
266
|
+
}
|
|
267
|
+
async withPreparedRadiantRuntime(render) {
|
|
268
|
+
await this.radiantSsrPolicy.prepareRuntime();
|
|
269
|
+
return await render();
|
|
270
|
+
}
|
|
271
|
+
createIntrinsicCustomElementRenderHook() {
|
|
272
|
+
return ({ instance }) => {
|
|
273
|
+
return instance ? this.radiantSsrPolicy.renderIntrinsicElementMarkup(instance) : undefined;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Records the source files that produced the current render so
|
|
278
|
+
* {@link EcopagesJsxHmrStrategy} can match later watcher events without
|
|
279
|
+
* re-walking the component tree.
|
|
280
|
+
*/
|
|
281
|
+
recordHmrOwnership(components) {
|
|
282
|
+
this.renderSession.mergeHmrOwnership(components);
|
|
283
|
+
}
|
|
237
284
|
}
|
|
238
|
-
export {
|
|
239
|
-
EcopagesJsxRenderer
|
|
240
|
-
};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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';
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { IntegrationPlugin, type EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
|
|
2
|
+
import { type AssetDefinition } from '@ecopages/core/services/asset-processing-service';
|
|
3
|
+
import type { HmrStrategy } from '@ecopages/core/hmr/hmr-strategy';
|
|
4
|
+
import type { IHmrManager } from '@ecopages/core';
|
|
2
5
|
import type { JsxRenderable } from '@ecopages/jsx';
|
|
3
6
|
import { EcopagesJsxRenderer } from './ecopages-jsx-renderer.js';
|
|
4
7
|
import type { EcopagesJsxPluginOptions } from './ecopages-jsx.types.js';
|
|
@@ -21,6 +24,20 @@ export declare class EcopagesJsxPlugin extends IntegrationPlugin<JsxRenderable>
|
|
|
21
24
|
private getDependencies;
|
|
22
25
|
/** Ensures MDX build hooks are ready before Ecopages collects contributions. */
|
|
23
26
|
prepareBuildContributions(): Promise<void>;
|
|
27
|
+
/** Assigns grouped-build metadata for page-owned JSX content scripts. */
|
|
28
|
+
prepareAssetDependencies(dependencies: AssetDefinition[]): AssetDefinition[];
|
|
29
|
+
/** Registers JSX HMR strategy, dev-transform plugins, and Radiant SSR invalidation hooks. */
|
|
30
|
+
setHmrManager(hmrManager: IHmrManager): void;
|
|
31
|
+
/**
|
|
32
|
+
* Returns the JSX-integration HMR strategy.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* The strategy handles `layout-update` broadcasts for page sources, layout
|
|
36
|
+
* sources, and any component file in the active render tree. It defers
|
|
37
|
+
* registered script entrypoints to the generic JS strategy and includes /
|
|
38
|
+
* explicit server views to `ServerRenderedTemplateHmrStrategy`.
|
|
39
|
+
*/
|
|
40
|
+
getHmrStrategy(): HmrStrategy | undefined;
|
|
24
41
|
/**
|
|
25
42
|
* Registers MDX tooling and completes the base integration setup.
|
|
26
43
|
*/
|