@ecopages/ecopages-jsx 0.2.0-beta.31 → 0.2.0-beta.33
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/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.js +76 -52
- package/src/ecopages-jsx-hmr-strategy.d.ts +4 -21
- package/src/ecopages-jsx-hmr-strategy.js +60 -56
- package/src/ecopages-jsx-mdx.js +63 -75
- package/src/ecopages-jsx-radiant-ssr-policy.js +104 -97
- package/src/ecopages-jsx-render-session.js +75 -71
- package/src/ecopages-jsx-renderer.js +270 -273
- package/src/ecopages-jsx.constants.js +7 -4
- package/src/ecopages-jsx.plugin.d.ts +1 -1
- package/src/ecopages-jsx.plugin.js +160 -152
- package/src/ecopages-jsx.types.js +1 -0
- package/src/page-owned-content-script-grouping.js +56 -50
- package/src/radiant-registered-script-ssr-invalidation.d.ts +1 -1
- package/src/radiant-registered-script-ssr-invalidation.js +43 -32
- package/src/resolve-radiant-install-hydrator.js +3 -7
|
@@ -1,105 +1,112 @@
|
|
|
1
|
-
import { createMarkupNodeLike } from
|
|
2
|
-
import { createServerHydrationBindingState, withServerHydrationBindingState } from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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}.
|
|
12
|
+
*/
|
|
13
|
+
export class EcopagesJsxRadiantSsrPolicy {
|
|
14
|
+
runtimeModules;
|
|
15
|
+
runtimeModulesPromise;
|
|
16
|
+
enabled;
|
|
17
|
+
constructor(enabled) {
|
|
18
|
+
this.enabled = enabled;
|
|
16
19
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Ensures the Radiant SSR runtime is installed before a render needs it.
|
|
22
|
+
*/
|
|
23
|
+
async prepareRuntime() {
|
|
24
|
+
if (!this.enabled) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
await this.ensureRuntimeInstalled();
|
|
25
28
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Runs one render inside Radiant's server runtime when the policy is enabled.
|
|
31
|
+
*/
|
|
32
|
+
async withRuntime(render) {
|
|
33
|
+
if (!this.enabled) {
|
|
34
|
+
return render();
|
|
35
|
+
}
|
|
36
|
+
const runtimeModules = await this.runtimeModulesPromise;
|
|
37
|
+
if (!runtimeModules) {
|
|
38
|
+
return render();
|
|
39
|
+
}
|
|
40
|
+
return runtimeModules.withServerRadiantElementSsrRuntime(render);
|
|
29
41
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Converts one Radiant custom-element instance into trusted SSR markup.
|
|
44
|
+
*
|
|
45
|
+
* @remarks
|
|
46
|
+
* The returned node-like wrapper lets the JSX server renderer preserve the
|
|
47
|
+
* generated host HTML without escaping it back into plain text.
|
|
48
|
+
*/
|
|
49
|
+
renderIntrinsicElementMarkup(instance) {
|
|
50
|
+
const renderBridge = this.runtimeModules?.resolveRadiantElementRenderBridge(instance);
|
|
51
|
+
if (!renderBridge) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
return createMarkupNodeLike(withServerHydrationBindingState(createServerHydrationBindingState(), () => renderBridge.renderHostToString({
|
|
55
|
+
mode: 'hydrate',
|
|
56
|
+
})));
|
|
43
57
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
resolveRadiantElementRenderBridge: radiantElementSsrRuntimeModule.resolveRadiantElementRenderBridge,
|
|
67
|
-
withServerRadiantElementSsrRuntime: radiantElementSsrRuntimeModule.withServerRadiantElementSsrRuntime
|
|
68
|
-
};
|
|
69
|
-
this.runtimeModules = modules;
|
|
70
|
-
return modules;
|
|
71
|
-
})();
|
|
58
|
+
async ensureRuntimeInstalled() {
|
|
59
|
+
if (!this.runtimeModulesPromise) {
|
|
60
|
+
const radiantLightDomShimEntry = import.meta.resolve('@ecopages/radiant/server/light-dom-shim');
|
|
61
|
+
const radiantElementSsrRuntimeModuleUrl = new URL('./radiant-element-ssr-bridge.js', radiantLightDomShimEntry).href;
|
|
62
|
+
this.runtimeModulesPromise = (async () => {
|
|
63
|
+
const lightDomShimModule = await import(radiantLightDomShimEntry);
|
|
64
|
+
ensureRadiantLightDomGlobals(lightDomShimModule.installLightDomShim);
|
|
65
|
+
const radiantElementSsrRuntimeModule = (await import(radiantElementSsrRuntimeModuleUrl));
|
|
66
|
+
const modules = {
|
|
67
|
+
installLightDomShim: lightDomShimModule.installLightDomShim,
|
|
68
|
+
resolveRadiantElementRenderBridge: radiantElementSsrRuntimeModule.resolveRadiantElementRenderBridge,
|
|
69
|
+
withServerRadiantElementSsrRuntime: radiantElementSsrRuntimeModule.withServerRadiantElementSsrRuntime,
|
|
70
|
+
};
|
|
71
|
+
this.runtimeModules = modules;
|
|
72
|
+
return modules;
|
|
73
|
+
})();
|
|
74
|
+
}
|
|
75
|
+
await this.runtimeModulesPromise;
|
|
76
|
+
const lightDomShimModule = this.runtimeModules;
|
|
77
|
+
if (lightDomShimModule) {
|
|
78
|
+
ensureRadiantLightDomGlobals(lightDomShimModule.installLightDomShim);
|
|
79
|
+
}
|
|
72
80
|
}
|
|
73
|
-
await this.runtimeModulesPromise;
|
|
74
|
-
const lightDomShimModule = this.runtimeModules;
|
|
75
|
-
if (lightDomShimModule) {
|
|
76
|
-
ensureRadiantLightDomGlobals(lightDomShimModule.installLightDomShim);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Installs Radiant light-DOM constructors on `globalThis` once.
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* Custom-element constructors must be process-global; undoing them between tests
|
|
87
|
+
* is unsafe. Callers that clear globals for isolation must rely on a fresh policy
|
|
88
|
+
* instance so {@link EcopagesJsxRadiantSsrPolicy.prepareRuntime} re-runs install.
|
|
89
|
+
*/
|
|
80
90
|
function ensureRadiantLightDomGlobals(installLightDomShim) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
91
|
+
if (typeof globalThis.HTMLElement !== 'undefined') {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const window = installLightDomShim();
|
|
95
|
+
if (!window) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
Object.assign(globalThis, {
|
|
99
|
+
CSS: window.CSS,
|
|
100
|
+
CustomEvent: window.CustomEvent,
|
|
101
|
+
Document: window.Document,
|
|
102
|
+
Element: window.Element,
|
|
103
|
+
Event: window.Event,
|
|
104
|
+
EventTarget: window.EventTarget,
|
|
105
|
+
HTMLScriptElement: window.HTMLScriptElement,
|
|
106
|
+
HTMLElement: window.HTMLElement,
|
|
107
|
+
Node: window.Node,
|
|
108
|
+
document: window.document,
|
|
109
|
+
customElements: window.customElements,
|
|
110
|
+
window,
|
|
111
|
+
});
|
|
102
112
|
}
|
|
103
|
-
export {
|
|
104
|
-
EcopagesJsxRadiantSsrPolicy
|
|
105
|
-
};
|
|
@@ -1,78 +1,82 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from
|
|
2
|
-
import { getActiveSsrScopeValue, withActiveSsrScopeValue } from
|
|
3
|
-
const ECOPAGES_JSX_SSR_RENDER_STATE_KEY =
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { getActiveSsrScopeValue, withActiveSsrScopeValue } from '@ecopages/jsx/server';
|
|
3
|
+
export const ECOPAGES_JSX_SSR_RENDER_STATE_KEY = Symbol.for('@ecopages/ecopages-jsx.ssr-render-state');
|
|
4
4
|
const renderStateStorage = new AsyncLocalStorage();
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*/
|
|
20
|
-
withActiveScope(render) {
|
|
21
|
-
const activeState = renderStateStorage.getStore();
|
|
22
|
-
if (activeState) {
|
|
23
|
-
return withActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY, activeState, render);
|
|
5
|
+
/**
|
|
6
|
+
* Tracks JSX SSR asset-collection state for one active render flow.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* The renderer still mirrors this state into `@ecopages/jsx/server` so nested
|
|
10
|
+
* JSX renders and existing SSR-scope probes can observe it, but it also keeps
|
|
11
|
+
* an internal async-local copy. That fallback avoids losing renderer state when
|
|
12
|
+
* host runtimes such as Vite end up loading separate module identities for the
|
|
13
|
+
* JSX server helpers.
|
|
14
|
+
*/
|
|
15
|
+
export class EcopagesJsxRenderSession {
|
|
16
|
+
dedupeProcessedAssets;
|
|
17
|
+
constructor(dedupeProcessedAssets) {
|
|
18
|
+
this.dedupeProcessedAssets = dedupeProcessedAssets;
|
|
24
19
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Runs one render inside the active session scope.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* When a render is already active, the current session state is reused and
|
|
25
|
+
* mirrored back into the JSX SSR scope so nested `renderToString()` calls see
|
|
26
|
+
* the same asset frame stack. When no session exists yet, a new state is
|
|
27
|
+
* created and published through both the internal async-local store and the
|
|
28
|
+
* JSX SSR scope bridge.
|
|
29
|
+
*/
|
|
30
|
+
withActiveScope(render) {
|
|
31
|
+
const activeState = renderStateStorage.getStore();
|
|
32
|
+
if (activeState) {
|
|
33
|
+
return withActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY, activeState, render);
|
|
34
|
+
}
|
|
35
|
+
const jsxScopeState = getActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY);
|
|
36
|
+
if (jsxScopeState) {
|
|
37
|
+
return renderStateStorage.run(jsxScopeState, () => render());
|
|
38
|
+
}
|
|
39
|
+
const state = {
|
|
40
|
+
collectedAssetFrames: [],
|
|
41
|
+
};
|
|
42
|
+
return renderStateStorage.run(state, () => withActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY, state, render));
|
|
28
43
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
() => withActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY, state, render)
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
beginCollectedAssetFrame() {
|
|
38
|
-
const state = this.getState();
|
|
39
|
-
const frame = [];
|
|
40
|
-
state.collectedAssetFrames.push(frame);
|
|
41
|
-
return frame;
|
|
42
|
-
}
|
|
43
|
-
endCollectedAssetFrame(frame) {
|
|
44
|
-
const activeFrame = this.getState().collectedAssetFrames.pop();
|
|
45
|
-
if (!activeFrame || activeFrame !== frame) {
|
|
46
|
-
return this.dedupeProcessedAssets(frame);
|
|
44
|
+
beginCollectedAssetFrame() {
|
|
45
|
+
const state = this.getState();
|
|
46
|
+
const frame = [];
|
|
47
|
+
state.collectedAssetFrames.push(frame);
|
|
48
|
+
return frame;
|
|
47
49
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (activeFrame) {
|
|
55
|
-
activeFrame.push(...dedupedAssets);
|
|
50
|
+
endCollectedAssetFrame(frame) {
|
|
51
|
+
const activeFrame = this.getState().collectedAssetFrames.pop();
|
|
52
|
+
if (!activeFrame || activeFrame !== frame) {
|
|
53
|
+
return this.dedupeProcessedAssets(frame);
|
|
54
|
+
}
|
|
55
|
+
return this.dedupeProcessedAssets(activeFrame);
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
57
|
+
recordCollectedAssets(collectedAssets) {
|
|
58
|
+
const dedupedAssets = this.dedupeProcessedAssets(collectedAssets);
|
|
59
|
+
const state = this.getState();
|
|
60
|
+
const activeFrame = state.collectedAssetFrames[state.collectedAssetFrames.length - 1];
|
|
61
|
+
if (activeFrame) {
|
|
62
|
+
activeFrame.push(...dedupedAssets);
|
|
63
|
+
}
|
|
64
|
+
return dedupedAssets;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolves the current render-session state from the local async scope first.
|
|
68
|
+
*
|
|
69
|
+
* @remarks
|
|
70
|
+
* The fallback read from `@ecopages/jsx/server` keeps compatibility with code
|
|
71
|
+
* that still inspects the public SSR scope directly, while the async-local
|
|
72
|
+
* store remains the authoritative source for renderer-owned bookkeeping.
|
|
73
|
+
*/
|
|
74
|
+
getState() {
|
|
75
|
+
const state = renderStateStorage.getStore() ??
|
|
76
|
+
getActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY);
|
|
77
|
+
if (!state) {
|
|
78
|
+
throw new Error('Ecopages JSX SSR render state is unavailable outside the active render scope.');
|
|
79
|
+
}
|
|
80
|
+
return state;
|
|
71
81
|
}
|
|
72
|
-
return state;
|
|
73
|
-
}
|
|
74
82
|
}
|
|
75
|
-
export {
|
|
76
|
-
ECOPAGES_JSX_SSR_RENDER_STATE_KEY,
|
|
77
|
-
EcopagesJsxRenderSession
|
|
78
|
-
};
|