@ecopages/ecopages-jsx 0.2.0-rc.11 → 0.2.0-rc.14
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/package.json +3 -3
- package/src/ecopages-jsx-hmr-ownership.d.ts +25 -13
- package/src/ecopages-jsx-hmr-ownership.js +52 -20
- package/src/ecopages-jsx-renderer.d.ts +0 -7
- package/src/ecopages-jsx-renderer.js +11 -46
- package/src/ecopages-jsx.plugin.d.ts +0 -1
- package/src/ecopages-jsx.plugin.js +2 -5
- package/src/ecopages-jsx-render-session.d.ts +0 -36
- package/src/ecopages-jsx-render-session.js +0 -84
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/ecopages-jsx",
|
|
3
|
-
"version": "0.2.0-rc.
|
|
3
|
+
"version": "0.2.0-rc.14",
|
|
4
4
|
"description": "JSX integration plugin for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
"directory": "packages/integrations/ecopages-jsx"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@ecopages/mdx": "0.2.0-rc.
|
|
37
|
+
"@ecopages/mdx": "0.2.0-rc.14",
|
|
38
38
|
"@mdx-js/mdx": "^3.1.1",
|
|
39
39
|
"vfile": "^6.0.3"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@ecopages/core": "0.2.0-rc.
|
|
42
|
+
"@ecopages/core": "0.2.0-rc.14",
|
|
43
43
|
"@ecopages/jsx": "^0.3.0-rc.9",
|
|
44
44
|
"@ecopages/radiant": "^0.3.0-rc.9"
|
|
45
45
|
}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tracks the source files that
|
|
2
|
+
* Tracks the source files that produced the most recent JSX render.
|
|
3
3
|
*
|
|
4
4
|
* @remarks
|
|
5
|
-
* The JSX
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* The JSX renderer runs each page, component, or view render inside
|
|
6
|
+
* {@link withEjsxHmrOwnershipScope} and records its components with
|
|
7
|
+
* {@link recordEjsxHmrOwnership}. `EcopagesJsxHmrStrategy` reads the result to
|
|
8
|
+
* decide whether a changed file affects SSR HTML without walking the component
|
|
9
|
+
* tree on every watcher event.
|
|
9
10
|
*
|
|
10
|
-
* The state is module-scoped because the HMR strategy
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* The state is module-scoped because the renderer and the HMR strategy share no
|
|
12
|
+
* other lifecycle anchor. Publishing replaces it only when the file set changes,
|
|
13
|
+
* so repeated renders of the same page keep the existing state.
|
|
14
|
+
*
|
|
15
|
+
* Only the latest top-level render is kept. With several pages open, a file
|
|
16
|
+
* used only by a page that rendered earlier is not matched until that page
|
|
17
|
+
* renders again.
|
|
14
18
|
*/
|
|
15
19
|
import type { EcoComponent } from '@ecopages/core';
|
|
16
20
|
export type EjsxHmrOwnershipState = {
|
|
@@ -32,13 +36,21 @@ export declare function getEjsxHmrOwnership(): EjsxHmrOwnershipState;
|
|
|
32
36
|
*/
|
|
33
37
|
export declare function updateEjsxHmrOwnership(components: ReadonlyArray<EcoComponent | Partial<EcoComponent> | undefined>): void;
|
|
34
38
|
/**
|
|
35
|
-
*
|
|
39
|
+
* Runs one render and publishes the files it recorded once the outermost scope ends.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* Nested renders, such as components and foreign subtrees resolved inside a
|
|
43
|
+
* page, join the outer scope, so the published set covers the whole render tree
|
|
44
|
+
* instead of the last nested render.
|
|
36
45
|
*/
|
|
37
|
-
export declare function
|
|
46
|
+
export declare function withEjsxHmrOwnershipScope<T>(render: () => T | Promise<T>): Promise<T>;
|
|
38
47
|
/**
|
|
39
|
-
*
|
|
48
|
+
* Adds the source files of `components`, their declared dependencies and their
|
|
49
|
+
* layouts to the active render scope.
|
|
50
|
+
*
|
|
51
|
+
* @throws Error when called outside {@link withEjsxHmrOwnershipScope}.
|
|
40
52
|
*/
|
|
41
|
-
export declare function
|
|
53
|
+
export declare function recordEjsxHmrOwnership(components: ReadonlyArray<EcoComponent | undefined>): void;
|
|
42
54
|
/**
|
|
43
55
|
* Resets the state to empty. Used by tests and by HMR manager teardown.
|
|
44
56
|
*/
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tracks the source files that
|
|
2
|
+
* Tracks the source files that produced the most recent JSX render.
|
|
3
3
|
*
|
|
4
4
|
* @remarks
|
|
5
|
-
* The JSX
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* The JSX renderer runs each page, component, or view render inside
|
|
6
|
+
* {@link withEjsxHmrOwnershipScope} and records its components with
|
|
7
|
+
* {@link recordEjsxHmrOwnership}. `EcopagesJsxHmrStrategy` reads the result to
|
|
8
|
+
* decide whether a changed file affects SSR HTML without walking the component
|
|
9
|
+
* tree on every watcher event.
|
|
9
10
|
*
|
|
10
|
-
* The state is module-scoped because the HMR strategy
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* The state is module-scoped because the renderer and the HMR strategy share no
|
|
12
|
+
* other lifecycle anchor. Publishing replaces it only when the file set changes,
|
|
13
|
+
* so repeated renders of the same page keep the existing state.
|
|
14
|
+
*
|
|
15
|
+
* Only the latest top-level render is kept. With several pages open, a file
|
|
16
|
+
* used only by a page that rendered earlier is not matched until that page
|
|
17
|
+
* renders again.
|
|
14
18
|
*/
|
|
19
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
15
20
|
import { rapidhash } from '@ecopages/core/hash';
|
|
16
21
|
import { collectComponentConfigFilePaths } from '@ecopages/core/route-renderer/page-loading/file-scoped-dependency-components';
|
|
17
22
|
const EMPTY_STATE = {
|
|
@@ -19,6 +24,7 @@ const EMPTY_STATE = {
|
|
|
19
24
|
hash: '',
|
|
20
25
|
};
|
|
21
26
|
let currentState = EMPTY_STATE;
|
|
27
|
+
const pendingFileOwners = new AsyncLocalStorage();
|
|
22
28
|
/**
|
|
23
29
|
* Returns the most recently recorded active render tree.
|
|
24
30
|
*/
|
|
@@ -38,17 +44,49 @@ export function updateEjsxHmrOwnership(components) {
|
|
|
38
44
|
publishEjsxHmrOwnership(collectFileOwners(components));
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
41
|
-
*
|
|
47
|
+
* Runs one render and publishes the files it recorded once the outermost scope ends.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* Nested renders, such as components and foreign subtrees resolved inside a
|
|
51
|
+
* page, join the outer scope, so the published set covers the whole render tree
|
|
52
|
+
* instead of the last nested render.
|
|
42
53
|
*/
|
|
43
|
-
export function
|
|
54
|
+
export async function withEjsxHmrOwnershipScope(render) {
|
|
55
|
+
if (pendingFileOwners.getStore()) {
|
|
56
|
+
return await render();
|
|
57
|
+
}
|
|
58
|
+
const fileOwners = new Set();
|
|
59
|
+
try {
|
|
60
|
+
return await pendingFileOwners.run(fileOwners, render);
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
if (fileOwners.size > 0) {
|
|
64
|
+
publishEjsxHmrOwnership(fileOwners);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Adds the source files of `components`, their declared dependencies and their
|
|
70
|
+
* layouts to the active render scope.
|
|
71
|
+
*
|
|
72
|
+
* @throws Error when called outside {@link withEjsxHmrOwnershipScope}.
|
|
73
|
+
*/
|
|
74
|
+
export function recordEjsxHmrOwnership(components) {
|
|
75
|
+
const fileOwners = pendingFileOwners.getStore();
|
|
76
|
+
if (!fileOwners) {
|
|
77
|
+
throw new Error('Ecopages JSX HMR ownership can only be recorded inside an active render scope.');
|
|
78
|
+
}
|
|
44
79
|
for (const file of collectFileOwners(components)) {
|
|
45
|
-
|
|
80
|
+
fileOwners.add(file);
|
|
46
81
|
}
|
|
47
82
|
}
|
|
48
83
|
/**
|
|
49
|
-
*
|
|
84
|
+
* Resets the state to empty. Used by tests and by HMR manager teardown.
|
|
50
85
|
*/
|
|
51
|
-
export function
|
|
86
|
+
export function resetEjsxHmrOwnership() {
|
|
87
|
+
currentState = EMPTY_STATE;
|
|
88
|
+
}
|
|
89
|
+
function publishEjsxHmrOwnership(files) {
|
|
52
90
|
const hash = hashFileSet(files);
|
|
53
91
|
if (hash === currentState.hash) {
|
|
54
92
|
return;
|
|
@@ -58,12 +96,6 @@ export function publishEjsxHmrOwnership(files) {
|
|
|
58
96
|
hash,
|
|
59
97
|
};
|
|
60
98
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Resets the state to empty. Used by tests and by HMR manager teardown.
|
|
63
|
-
*/
|
|
64
|
-
export function resetEjsxHmrOwnership() {
|
|
65
|
-
currentState = EMPTY_STATE;
|
|
66
|
-
}
|
|
67
99
|
function collectFileOwners(components) {
|
|
68
100
|
return collectComponentConfigFilePaths(components, { includeLayouts: true });
|
|
69
101
|
}
|
|
@@ -14,7 +14,6 @@ export type { EcopagesJsxRendererConfig, EcopagesJsxRendererOptions } from './ec
|
|
|
14
14
|
export declare class EcopagesJsxRenderer extends IntegrationRenderer<JsxRenderable> {
|
|
15
15
|
name: string;
|
|
16
16
|
private readonly mdxExtensions;
|
|
17
|
-
private readonly renderSession;
|
|
18
17
|
private readonly radiantSsrPolicy;
|
|
19
18
|
private readonly ssrScriptPreloader;
|
|
20
19
|
/**
|
|
@@ -52,10 +51,4 @@ export declare class EcopagesJsxRenderer extends IntegrationRenderer<JsxRenderab
|
|
|
52
51
|
private withCustomElementRenderHook;
|
|
53
52
|
private withPreparedRadiantRuntime;
|
|
54
53
|
private createIntrinsicCustomElementRenderHook;
|
|
55
|
-
/**
|
|
56
|
-
* Records the source files that produced the current render so
|
|
57
|
-
* {@link EcopagesJsxHmrStrategy} can match later watcher events without
|
|
58
|
-
* re-walking the component tree.
|
|
59
|
-
*/
|
|
60
|
-
private recordHmrOwnership;
|
|
61
54
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { IntegrationRenderer, } from '@ecopages/core/route-renderer/orchestration/integration-renderer';
|
|
2
2
|
import { resolveDocumentShellLayouts } from '@ecopages/core/route-renderer/orchestration/document-shell/layout-shell-props.service';
|
|
3
|
-
import { getForeignSubtreeResolutionContextKey, resolveOwningIntegrationRenderer, } from '@ecopages/core/route-renderer/orchestration/foreign-child/owning-renderer-resolution';
|
|
4
3
|
import { createMarkupNodeLike } from '@ecopages/jsx';
|
|
5
4
|
import { renderToString, withServerCustomElementRenderHook } from '@ecopages/jsx/server';
|
|
6
5
|
import { ECOPAGES_JSX_PLUGIN_NAME } from './ecopages-jsx.constants.js';
|
|
7
6
|
import { isMdxFile, normalizeMdxPageModule, } from './ecopages-jsx-mdx.js';
|
|
8
|
-
import {
|
|
7
|
+
import { recordEjsxHmrOwnership, withEjsxHmrOwnershipScope } from './ecopages-jsx-hmr-ownership.js';
|
|
9
8
|
import { EcopagesJsxRadiantSsrPolicy } from './ecopages-jsx-radiant-ssr-policy.js';
|
|
10
9
|
import { CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES, CustomElementScriptPreloader, } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-script-preloader';
|
|
11
10
|
import { createSharedServerModuleImporter } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-server-module-importer';
|
|
@@ -18,7 +17,6 @@ import { createSharedServerModuleImporter } from '@ecopages/core/route-renderer/
|
|
|
18
17
|
export class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
19
18
|
name = ECOPAGES_JSX_PLUGIN_NAME;
|
|
20
19
|
mdxExtensions;
|
|
21
|
-
renderSession;
|
|
22
20
|
radiantSsrPolicy;
|
|
23
21
|
ssrScriptPreloader;
|
|
24
22
|
/**
|
|
@@ -79,19 +77,11 @@ export class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
79
77
|
*/
|
|
80
78
|
async renderQueuedForeignSubtreeChildren(children, queuedResolutionsByToken, resolveToken) {
|
|
81
79
|
if (children === undefined) {
|
|
82
|
-
return {
|
|
80
|
+
return {};
|
|
83
81
|
}
|
|
84
|
-
|
|
85
|
-
if (typeof children === 'string') {
|
|
86
|
-
html = children;
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
html = await this.renderJsx(children);
|
|
90
|
-
}
|
|
91
|
-
html = await this.foreignSubtreeExecutionService.resolveQueuedTokens(html, queuedResolutionsByToken, resolveToken);
|
|
82
|
+
const html = typeof children === 'string' ? children : await this.renderJsx(children);
|
|
92
83
|
return {
|
|
93
|
-
|
|
94
|
-
html,
|
|
84
|
+
html: await this.foreignSubtreeExecutionService.resolveQueuedTokens(html, queuedResolutionsByToken, resolveToken),
|
|
95
85
|
};
|
|
96
86
|
}
|
|
97
87
|
createForeignChildRuntime(options) {
|
|
@@ -121,7 +111,6 @@ export class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
121
111
|
runtimeOrigin,
|
|
122
112
|
});
|
|
123
113
|
this.mdxExtensions = jsxConfig?.mdxExtensions ?? ['.mdx'];
|
|
124
|
-
this.renderSession = new EcopagesJsxRenderSession();
|
|
125
114
|
const radiantSsrEnabled = jsxConfig?.radiantSsrEnabled ?? false;
|
|
126
115
|
this.radiantSsrPolicy = jsxConfig?.radiantSsrPolicy ?? new EcopagesJsxRadiantSsrPolicy(radiantSsrEnabled);
|
|
127
116
|
const preferSourceImports = typeof Bun !== 'undefined';
|
|
@@ -161,7 +150,7 @@ export class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
161
150
|
options.HtmlTemplate,
|
|
162
151
|
...(options.resolvedPageDependencyComponents ?? []),
|
|
163
152
|
]);
|
|
164
|
-
return await
|
|
153
|
+
return await withEjsxHmrOwnershipScope(async () => {
|
|
165
154
|
try {
|
|
166
155
|
const result = await this.renderPageWithDocumentShell({
|
|
167
156
|
page: {
|
|
@@ -188,7 +177,7 @@ export class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
188
177
|
metadata: options.metadata,
|
|
189
178
|
pageProps: options.pageProps ?? {},
|
|
190
179
|
});
|
|
191
|
-
|
|
180
|
+
recordEjsxHmrOwnership([
|
|
192
181
|
options.Page,
|
|
193
182
|
options.Layout,
|
|
194
183
|
options.HtmlTemplate,
|
|
@@ -205,7 +194,7 @@ export class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
205
194
|
async renderComponent(input) {
|
|
206
195
|
return await this.withPreparedRadiantRuntime(async () => {
|
|
207
196
|
await this.ssrScriptPreloader.preloadSsrScripts([input.component]);
|
|
208
|
-
return await
|
|
197
|
+
return await withEjsxHmrOwnershipScope(async () => {
|
|
209
198
|
try {
|
|
210
199
|
if (typeof input.component !== 'function') {
|
|
211
200
|
throw new TypeError('JSX renderer expected a callable component.');
|
|
@@ -221,23 +210,7 @@ export class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
221
210
|
};
|
|
222
211
|
const content = await this.withCustomElementRenderHook(() => component(componentProps));
|
|
223
212
|
const html = await this.renderJsx(content);
|
|
224
|
-
const queuedForeignSubtreeResolution = await this.
|
|
225
|
-
currentIntegrationName: this.name,
|
|
226
|
-
html,
|
|
227
|
-
runtimeContext: this.foreignSubtreeExecutionService.getQueuedRuntimeContext(input, getForeignSubtreeResolutionContextKey(this.name)),
|
|
228
|
-
queueLabel: 'Ecopages JSX',
|
|
229
|
-
getOwningRenderer: (integrationName, rendererCache) => resolveOwningIntegrationRenderer({
|
|
230
|
-
appConfig: this.appConfig,
|
|
231
|
-
runtimeOrigin: this.runtimeOrigin,
|
|
232
|
-
currentIntegrationName: this.name,
|
|
233
|
-
currentRenderer: this,
|
|
234
|
-
integrationName,
|
|
235
|
-
cache: rendererCache,
|
|
236
|
-
}),
|
|
237
|
-
applyAttributesToFirstElement: (resolvedHtml, attributes) => this.htmlTransformer.applyAttributesToFirstElement(resolvedHtml, attributes),
|
|
238
|
-
dedupeProcessedAssets: (assets) => this.htmlTransformer.dedupeProcessedAssets(assets),
|
|
239
|
-
renderQueuedChildren: (children, _runtimeContext, queuedResolutionsByToken, resolveToken) => this.renderQueuedForeignSubtreeChildren(children, queuedResolutionsByToken, resolveToken),
|
|
240
|
-
});
|
|
213
|
+
const queuedForeignSubtreeResolution = await this.resolveQueuedForeignSubtrees(html, this.getQueuedForeignSubtreeContext(input), (children, _runtimeContext, queuedResolutionsByToken, resolveToken) => this.renderQueuedForeignSubtreeChildren(children, queuedResolutionsByToken, resolveToken));
|
|
241
214
|
const componentAssets = input.component.config?.dependencies &&
|
|
242
215
|
typeof this.assetProcessingService?.processDependencies === 'function'
|
|
243
216
|
? await this.processComponentDependencies([input.component])
|
|
@@ -246,7 +219,7 @@ export class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
246
219
|
...queuedForeignSubtreeResolution.assets,
|
|
247
220
|
...componentAssets,
|
|
248
221
|
]);
|
|
249
|
-
|
|
222
|
+
recordEjsxHmrOwnership([input.component]);
|
|
250
223
|
return this.finalizeIslandComponentRender(input, {
|
|
251
224
|
html: queuedForeignSubtreeResolution.html,
|
|
252
225
|
canAttachAttributes: true,
|
|
@@ -266,7 +239,7 @@ export class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
266
239
|
const viewComponent = view;
|
|
267
240
|
const layouts = viewComponent.config?.layouts;
|
|
268
241
|
await this.ssrScriptPreloader.preloadSsrScripts([view, ...(layouts ?? [])]);
|
|
269
|
-
return await
|
|
242
|
+
return await withEjsxHmrOwnershipScope(async () => {
|
|
270
243
|
try {
|
|
271
244
|
if (typeof view !== 'function') {
|
|
272
245
|
throw new TypeError('JSX renderer expected a callable view component.');
|
|
@@ -277,7 +250,7 @@ export class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
277
250
|
ctx,
|
|
278
251
|
layout: layouts?.[layouts.length - 1],
|
|
279
252
|
});
|
|
280
|
-
|
|
253
|
+
recordEjsxHmrOwnership([view]);
|
|
281
254
|
return response;
|
|
282
255
|
}
|
|
283
256
|
catch (error) {
|
|
@@ -301,12 +274,4 @@ export class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
301
274
|
return instance ? this.radiantSsrPolicy.renderIntrinsicElementMarkup(instance) : undefined;
|
|
302
275
|
};
|
|
303
276
|
}
|
|
304
|
-
/**
|
|
305
|
-
* Records the source files that produced the current render so
|
|
306
|
-
* {@link EcopagesJsxHmrStrategy} can match later watcher events without
|
|
307
|
-
* re-walking the component tree.
|
|
308
|
-
*/
|
|
309
|
-
recordHmrOwnership(components) {
|
|
310
|
-
this.renderSession.mergeHmrOwnership(components);
|
|
311
|
-
}
|
|
312
277
|
}
|
|
@@ -10,7 +10,6 @@ export type { EcopagesJsxMdxOptions, EcopagesJsxPluginOptions, EcopagesJsxRender
|
|
|
10
10
|
export declare class EcopagesJsxPlugin extends IntegrationPlugin<JsxRenderable> {
|
|
11
11
|
renderer: typeof EcopagesJsxRenderer;
|
|
12
12
|
private includeRadiant;
|
|
13
|
-
private mdxEnabled;
|
|
14
13
|
private mdxCompilerOptions?;
|
|
15
14
|
private mdxExtensions;
|
|
16
15
|
private mdxLoaderPlugin?;
|
|
@@ -30,7 +30,6 @@ const resolvePluginOptions = (options) => {
|
|
|
30
30
|
...baseConfig,
|
|
31
31
|
extensions,
|
|
32
32
|
includeRadiant: radiant ?? true,
|
|
33
|
-
mdxEnabled,
|
|
34
33
|
mdxExtensions,
|
|
35
34
|
mdxCompilerOptions: mdxEnabled && mdx ? resolveMdxCompilerOptions(mdx) : undefined,
|
|
36
35
|
};
|
|
@@ -39,7 +38,6 @@ const resolvePluginOptions = (options) => {
|
|
|
39
38
|
export class EcopagesJsxPlugin extends IntegrationPlugin {
|
|
40
39
|
renderer = EcopagesJsxRenderer;
|
|
41
40
|
includeRadiant;
|
|
42
|
-
mdxEnabled;
|
|
43
41
|
mdxCompilerOptions;
|
|
44
42
|
mdxExtensions;
|
|
45
43
|
mdxLoaderPlugin;
|
|
@@ -59,7 +57,7 @@ export class EcopagesJsxPlugin extends IntegrationPlugin {
|
|
|
59
57
|
}
|
|
60
58
|
constructor(options) {
|
|
61
59
|
const config = resolvePluginOptions(options);
|
|
62
|
-
const { extensions, includeRadiant,
|
|
60
|
+
const { extensions, includeRadiant, mdxExtensions, mdxCompilerOptions, ...baseConfig } = config;
|
|
63
61
|
super({
|
|
64
62
|
name: ECOPAGES_JSX_PLUGIN_NAME,
|
|
65
63
|
extensions,
|
|
@@ -67,7 +65,6 @@ export class EcopagesJsxPlugin extends IntegrationPlugin {
|
|
|
67
65
|
...baseConfig,
|
|
68
66
|
});
|
|
69
67
|
this.includeRadiant = includeRadiant;
|
|
70
|
-
this.mdxEnabled = mdxEnabled;
|
|
71
68
|
this.mdxExtensions = mdxExtensions;
|
|
72
69
|
this.mdxCompilerOptions = mdxCompilerOptions;
|
|
73
70
|
if (this.includeRadiant) {
|
|
@@ -148,7 +145,7 @@ export class EcopagesJsxPlugin extends IntegrationPlugin {
|
|
|
148
145
|
await super.setup();
|
|
149
146
|
}
|
|
150
147
|
ensureMdxLoaderPlugin() {
|
|
151
|
-
if (!this.
|
|
148
|
+
if (!this.mdxCompilerOptions || this.mdxLoaderPlugin) {
|
|
152
149
|
return;
|
|
153
150
|
}
|
|
154
151
|
if (!this.appConfig?.rootDir) {
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { EcoComponent } from '@ecopages/core';
|
|
2
|
-
export declare const ECOPAGES_JSX_SSR_RENDER_STATE_KEY: unique symbol;
|
|
3
|
-
/**
|
|
4
|
-
* Tracks JSX SSR HMR-ownership state for one active render flow.
|
|
5
|
-
*
|
|
6
|
-
* @remarks
|
|
7
|
-
* The renderer still mirrors this state into `@ecopages/jsx/server` so nested
|
|
8
|
-
* JSX renders and existing SSR-scope probes can observe it, but it also keeps
|
|
9
|
-
* an internal async-local copy. That fallback avoids losing renderer state when
|
|
10
|
-
* host runtimes such as Vite end up loading separate module identities for the
|
|
11
|
-
* JSX server helpers.
|
|
12
|
-
*/
|
|
13
|
-
export declare class EcopagesJsxRenderSession {
|
|
14
|
-
/**
|
|
15
|
-
* Runs one render inside the active session scope.
|
|
16
|
-
*
|
|
17
|
-
* @remarks
|
|
18
|
-
* When a render is already active, the current session state is reused and
|
|
19
|
-
* mirrored back into the JSX SSR scope so nested `renderToString()` calls see
|
|
20
|
-
* the same HMR-ownership set. When no session exists yet, a new state is
|
|
21
|
-
* created and published through both the internal async-local store and the
|
|
22
|
-
* JSX SSR scope bridge. HMR ownership accumulated during the outer scope is
|
|
23
|
-
* published once when that scope completes.
|
|
24
|
-
*/
|
|
25
|
-
withActiveScope<T>(render: () => T | Promise<T>): Promise<T>;
|
|
26
|
-
mergeHmrOwnership(components: ReadonlyArray<EcoComponent | undefined>): void;
|
|
27
|
-
/**
|
|
28
|
-
* Resolves the current render-session state from the local async scope first.
|
|
29
|
-
*
|
|
30
|
-
* @remarks
|
|
31
|
-
* The fallback read from `@ecopages/jsx/server` keeps compatibility with code
|
|
32
|
-
* that still inspects the public SSR scope directly, while the async-local
|
|
33
|
-
* store remains the authoritative source for renderer-owned bookkeeping.
|
|
34
|
-
*/
|
|
35
|
-
private getState;
|
|
36
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
import { getActiveSsrScopeValue, withActiveSsrScopeValue } from '@ecopages/jsx/server';
|
|
3
|
-
import { mergeEjsxHmrOwnership, publishEjsxHmrOwnership } from './ecopages-jsx-hmr-ownership.js';
|
|
4
|
-
export const ECOPAGES_JSX_SSR_RENDER_STATE_KEY = Symbol.for('@ecopages/ecopages-jsx.ssr-render-state');
|
|
5
|
-
const renderStateStorage = new AsyncLocalStorage();
|
|
6
|
-
function ensurePendingHmrFileOwners(state) {
|
|
7
|
-
if (!state.pendingHmrFileOwners) {
|
|
8
|
-
state.pendingHmrFileOwners = new Set();
|
|
9
|
-
}
|
|
10
|
-
return state.pendingHmrFileOwners;
|
|
11
|
-
}
|
|
12
|
-
function commitPendingHmrOwnership(state) {
|
|
13
|
-
const pending = state.pendingHmrFileOwners;
|
|
14
|
-
if (!pending || pending.size === 0) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
publishEjsxHmrOwnership(pending);
|
|
18
|
-
pending.clear();
|
|
19
|
-
}
|
|
20
|
-
async function runWithCommittedHmrOwnership(state, render) {
|
|
21
|
-
try {
|
|
22
|
-
return await render();
|
|
23
|
-
}
|
|
24
|
-
finally {
|
|
25
|
-
commitPendingHmrOwnership(state);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Tracks JSX SSR HMR-ownership state for one active render flow.
|
|
30
|
-
*
|
|
31
|
-
* @remarks
|
|
32
|
-
* The renderer still mirrors this state into `@ecopages/jsx/server` so nested
|
|
33
|
-
* JSX renders and existing SSR-scope probes can observe it, but it also keeps
|
|
34
|
-
* an internal async-local copy. That fallback avoids losing renderer state when
|
|
35
|
-
* host runtimes such as Vite end up loading separate module identities for the
|
|
36
|
-
* JSX server helpers.
|
|
37
|
-
*/
|
|
38
|
-
export class EcopagesJsxRenderSession {
|
|
39
|
-
/**
|
|
40
|
-
* Runs one render inside the active session scope.
|
|
41
|
-
*
|
|
42
|
-
* @remarks
|
|
43
|
-
* When a render is already active, the current session state is reused and
|
|
44
|
-
* mirrored back into the JSX SSR scope so nested `renderToString()` calls see
|
|
45
|
-
* the same HMR-ownership set. When no session exists yet, a new state is
|
|
46
|
-
* created and published through both the internal async-local store and the
|
|
47
|
-
* JSX SSR scope bridge. HMR ownership accumulated during the outer scope is
|
|
48
|
-
* published once when that scope completes.
|
|
49
|
-
*/
|
|
50
|
-
async withActiveScope(render) {
|
|
51
|
-
const activeState = renderStateStorage.getStore();
|
|
52
|
-
if (activeState) {
|
|
53
|
-
return await withActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY, activeState, render);
|
|
54
|
-
}
|
|
55
|
-
const jsxScopeState = getActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY);
|
|
56
|
-
if (jsxScopeState) {
|
|
57
|
-
ensurePendingHmrFileOwners(jsxScopeState);
|
|
58
|
-
return await runWithCommittedHmrOwnership(jsxScopeState, () => renderStateStorage.run(jsxScopeState, () => render()));
|
|
59
|
-
}
|
|
60
|
-
const state = {
|
|
61
|
-
pendingHmrFileOwners: new Set(),
|
|
62
|
-
};
|
|
63
|
-
return await runWithCommittedHmrOwnership(state, () => renderStateStorage.run(state, () => withActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY, state, render)));
|
|
64
|
-
}
|
|
65
|
-
mergeHmrOwnership(components) {
|
|
66
|
-
mergeEjsxHmrOwnership(ensurePendingHmrFileOwners(this.getState()), components);
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Resolves the current render-session state from the local async scope first.
|
|
70
|
-
*
|
|
71
|
-
* @remarks
|
|
72
|
-
* The fallback read from `@ecopages/jsx/server` keeps compatibility with code
|
|
73
|
-
* that still inspects the public SSR scope directly, while the async-local
|
|
74
|
-
* store remains the authoritative source for renderer-owned bookkeeping.
|
|
75
|
-
*/
|
|
76
|
-
getState() {
|
|
77
|
-
const state = renderStateStorage.getStore() ??
|
|
78
|
-
getActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY);
|
|
79
|
-
if (!state) {
|
|
80
|
-
throw new Error('Ecopages JSX SSR render state is unavailable outside the active render scope.');
|
|
81
|
-
}
|
|
82
|
-
return state;
|
|
83
|
-
}
|
|
84
|
-
}
|