@ecopages/ecopages-jsx 0.2.0-alpha.25 → 0.2.0-alpha.27
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/CHANGELOG.md +9 -5
- package/README.md +12 -3
- package/package.json +4 -4
- package/src/ecopages-jsx-mdx.d.ts +21 -0
- package/src/ecopages-jsx-mdx.js +113 -0
- package/src/ecopages-jsx-radiant-ssr-policy.d.ts +33 -0
- package/src/ecopages-jsx-radiant-ssr-policy.js +80 -0
- package/src/ecopages-jsx-render-session.d.ts +39 -0
- package/src/ecopages-jsx-render-session.js +78 -0
- package/src/ecopages-jsx-renderer.d.ts +11 -54
- package/src/ecopages-jsx-renderer.js +117 -330
- package/src/ecopages-jsx.plugin.d.ts +4 -33
- package/src/ecopages-jsx.plugin.js +21 -196
- package/src/ecopages-jsx.types.d.ts +6 -10
- package/src/services/jsx-runtime-bundle.service.d.ts +0 -54
- package/src/services/jsx-runtime-bundle.service.js +0 -251
package/CHANGELOG.md
CHANGED
|
@@ -8,25 +8,29 @@
|
|
|
8
8
|
|
|
9
9
|
### Bug Fixes
|
|
10
10
|
|
|
11
|
+
- Moved Ecopages JSX intrinsic custom-element asset bookkeeping into the active JSX SSR render scope and reinstalls the Radiant light-DOM shim whenever SSR runtime setup reruns so nested renders stay aligned with the current server render contract.
|
|
12
|
+
- Fixed intrinsic custom-element SSR asset hooks to fall back cleanly when they run after the active JSX render frame has already unwound, avoiding spurious server warnings during docs renders.
|
|
11
13
|
- Fixed lazy Ecopages JSX custom-element dependencies to stay as standalone assets instead of being folded into page-owned bundles, restoring trigger-driven loading for docs components like `theme-toggle`.
|
|
12
14
|
- Fixed Ecopages JSX page-owned browser bundles to inline their JSX and Radiant runtime imports while skipping separate intrinsic custom-element script tags when the current component tree already imports those scripts.
|
|
13
15
|
- Fixed intrinsic custom-element script suppression to honor dependency-declared script ownership instead of relying only on source import scanning.
|
|
16
|
+
- Fixed Radiant custom-element SSR bridging so `prop:` values like array props render through the server host bridge without requiring wrapper-level attribute serialization fallbacks.
|
|
14
17
|
- Aligned the Ecopages JSX browser runtime bundle with the upstream `@ecopages/jsx` runtime shipped by current alpha releases.
|
|
15
18
|
- Aligned Ecopages JSX peer dependency ranges with the current `@ecopages/jsx` and `@ecopages/radiant` alpha releases.
|
|
16
19
|
- Aligned Radiant SSR and hydration wiring with the public `@ecopages/radiant/server/render-component` and `@ecopages/radiant/client/hydrator` entrypoints so JSX apps install an explicit client hydrator bootstrap instead of relying on implicit side effects.
|
|
17
20
|
- Updated the Ecopages JSX Radiant browser runtime for the `RadiantElement` and `RadiantController` API surface and switched the explicit hydrator bootstrap to `@ecopages/radiant/client/install-hydrator`.
|
|
18
21
|
- Fixed Radiant SSR page inspection to install the light-DOM shim before JSX page modules are imported outside the normal render pass.
|
|
19
22
|
- Restored direct `EcopagesJsxPlugin` construction so the exported class still accepts the public plugin options shape.
|
|
20
|
-
-
|
|
21
|
-
-
|
|
23
|
+
- Aligned Ecopages JSX intrinsic custom-element loading with explicit `dependencies.scripts` ownership instead of implicit tag-to-script discovery.
|
|
24
|
+
- Removed implicit JSX integration-generated intrinsic custom-element browser entries so client custom-element loading now follows the normal asset pipeline.
|
|
22
25
|
- Fixed the Ecopages JSX browser runtime bundle so Radiant custom-element scripts no longer fail on a duplicate `jsxDEV` export cycle.
|
|
23
|
-
- Fixed Ecopages JSX
|
|
26
|
+
- Fixed Ecopages JSX foreign-subtree payload compatibility coverage and removed the plugin/renderer integration-name import cycle.
|
|
24
27
|
|
|
25
28
|
### Refactoring
|
|
26
29
|
|
|
27
|
-
- Removed the JSX browser import-map asset
|
|
30
|
+
- Removed the shared JSX runtime bundle service and browser import-map asset in favor of per-script browser entries that prepend `@ecopages/radiant/client/install-hydrator` only when Radiant SSR is enabled.
|
|
28
31
|
- Replaced Ecopages JSX renderer static and post-construction configuration with instance-owned renderer wiring and extracted shared plugin and renderer types into a dedicated module.
|
|
32
|
+
- Extracted JSX renderer SSR asset-frame scope handling into a dedicated render-session module.
|
|
29
33
|
|
|
30
34
|
### Tests
|
|
31
35
|
|
|
32
|
-
- Added renderer-level coverage for the
|
|
36
|
+
- Added renderer-level coverage for the foreign-subtree payload compatibility contract.
|
package/README.md
CHANGED
|
@@ -5,10 +5,10 @@ Integration plugin for [@ecopages/jsx](https://www.npmjs.com/package/@ecopages/j
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
bun add @ecopages/ecopages-jsx @ecopages/radiant
|
|
8
|
+
bun add @ecopages/ecopages-jsx @ecopages/jsx @ecopages/radiant
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
`@ecopages/radiant`
|
|
11
|
+
`@ecopages/jsx` and `@ecopages/radiant` are required peer dependencies for this package.
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
@@ -62,6 +62,15 @@ Set `radiant: false` when your JSX pages do not need Radiant SSR or the Radiant
|
|
|
62
62
|
|
|
63
63
|
The plugin bootstrap is intentionally explicit rather than depending on custom-element modules to install the Radiant hydrator opportunistically.
|
|
64
64
|
|
|
65
|
+
For server-side custom-element examples, prefer explicit SSR modes over the older boolean-only contract.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
new MyElement().renderHostToString({ mode: 'plain' });
|
|
69
|
+
new MyElement().renderHostToString({ mode: 'hydrate' });
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The legacy `hydrate: true` option remains compatible, but `mode` is the current SSR contract. Registered intrinsic tags that contain a dash are SSR candidates, and framework-specific host rendering should be adapted through the server custom-element render hook instead of hardcoding framework branches into JSX page code.
|
|
73
|
+
|
|
65
74
|
## MDX Support
|
|
66
75
|
|
|
67
76
|
Enable MDX to treat `.mdx` files as JSX routes compiled against the `@ecopages/jsx` runtime.
|
|
@@ -86,7 +95,7 @@ export default config;
|
|
|
86
95
|
|
|
87
96
|
## Mixed Rendering
|
|
88
97
|
|
|
89
|
-
Ecopages JSX can own the outer page shell or just
|
|
98
|
+
Ecopages JSX can own the outer page shell or just a nested foreign subtree. When another integration reaches a JSX-owned foreign child, Ecopages hands that foreign subtree back to the JSX renderer so it can serialize the correct output before the outer renderer resumes.
|
|
90
99
|
|
|
91
100
|
Important:
|
|
92
101
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/ecopages-jsx",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.27",
|
|
4
4
|
"description": "JSX integration plugin for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"vfile": "^6.0.3"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"@ecopages/core": "0.2.0-alpha.
|
|
25
|
-
"@ecopages/jsx": "0.3.0-alpha.
|
|
26
|
-
"@ecopages/radiant": "0.3.0-alpha.
|
|
24
|
+
"@ecopages/core": "0.2.0-alpha.27",
|
|
25
|
+
"@ecopages/jsx": "0.3.0-alpha.13",
|
|
26
|
+
"@ecopages/radiant": "0.3.0-alpha.13"
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CompileOptions } from '@mdx-js/mdx';
|
|
2
|
+
import type { EcoComponent, EcoComponentConfig, EcoFunctionComponent, EcoPageFile, GetMetadata } from '@ecopages/core';
|
|
3
|
+
import type { EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
|
|
4
|
+
import type { JsxRenderable } from '@ecopages/jsx';
|
|
5
|
+
import type { EcopagesJsxMdxCompileOptions, EcopagesJsxMdxOptions } from './ecopages-jsx.types.js';
|
|
6
|
+
export type ResolvedMdxCompileOptions = EcopagesJsxMdxCompileOptions & Pick<CompileOptions, 'jsxImportSource' | 'jsxRuntime'>;
|
|
7
|
+
export type AsyncEcoComponent<P = Record<string, unknown>, R = JsxRenderable> = EcoFunctionComponent<P, R | Promise<R>>;
|
|
8
|
+
export type EcopagesJsxMdxPageModule = EcoPageFile<{
|
|
9
|
+
config?: EcoComponentConfig;
|
|
10
|
+
layout?: EcoComponent;
|
|
11
|
+
getMetadata?: GetMetadata;
|
|
12
|
+
}>;
|
|
13
|
+
export declare const createMdxExtensionFilter: (extensions: string[], options?: {
|
|
14
|
+
allowQueryString?: boolean;
|
|
15
|
+
}) => RegExp;
|
|
16
|
+
export declare const appendMdxExtensions: (target: string[], mdxExtensions: string[]) => void;
|
|
17
|
+
export declare const resolveMdxCompilerOptions: (mdxOptions: EcopagesJsxMdxOptions) => ResolvedMdxCompileOptions;
|
|
18
|
+
export declare const createMdxLoaderPlugin: (compilerOptions: ResolvedMdxCompileOptions, extensions: string[]) => EcoBuildPlugin;
|
|
19
|
+
export declare const registerBunMdxPlugin: (compilerOptions: ResolvedMdxCompileOptions, extensions: string[]) => Promise<void>;
|
|
20
|
+
export declare const isMdxFile: (filePath: string, extensions: string[]) => boolean;
|
|
21
|
+
export declare const normalizeMdxPageModule: (file: string, module: EcopagesJsxMdxPageModule) => EcopagesJsxMdxPageModule;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { rapidhash } from "@ecopages/core/hash";
|
|
4
|
+
import { VFile } from "vfile";
|
|
5
|
+
import { ECOPAGES_JSX_PLUGIN_NAME } from "./ecopages-jsx.constants.js";
|
|
6
|
+
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7
|
+
const mergePluginLists = (...lists) => {
|
|
8
|
+
const merged = lists.flatMap((list) => list ? [...list] : []);
|
|
9
|
+
return merged.length > 0 ? merged : void 0;
|
|
10
|
+
};
|
|
11
|
+
const createMdxExtensionFilter = (extensions, options) => {
|
|
12
|
+
const escaped = extensions.map(escapeRegex);
|
|
13
|
+
const suffix = options?.allowQueryString ? "(\\?.*)?$" : "$";
|
|
14
|
+
return new RegExp(`(${escaped.join("|")})${suffix}`);
|
|
15
|
+
};
|
|
16
|
+
const appendMdxExtensions = (target, mdxExtensions) => {
|
|
17
|
+
for (const ext of mdxExtensions) {
|
|
18
|
+
if (!target.includes(ext)) {
|
|
19
|
+
target.push(ext);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const resolveMdxCompilerOptions = (mdxOptions) => {
|
|
24
|
+
const { compilerOptions, remarkPlugins, rehypePlugins, recmaPlugins } = mdxOptions;
|
|
25
|
+
const resolved = {
|
|
26
|
+
format: "detect",
|
|
27
|
+
outputFormat: "program",
|
|
28
|
+
...compilerOptions,
|
|
29
|
+
jsxImportSource: "@ecopages/jsx",
|
|
30
|
+
jsxRuntime: "automatic",
|
|
31
|
+
development: process.env.NODE_ENV === "development"
|
|
32
|
+
};
|
|
33
|
+
const mergedRemark = mergePluginLists(compilerOptions?.remarkPlugins, remarkPlugins);
|
|
34
|
+
const mergedRehype = mergePluginLists(compilerOptions?.rehypePlugins, rehypePlugins);
|
|
35
|
+
const mergedRecma = mergePluginLists(compilerOptions?.recmaPlugins, recmaPlugins);
|
|
36
|
+
if (mergedRemark) resolved.remarkPlugins = mergedRemark;
|
|
37
|
+
if (mergedRehype) resolved.rehypePlugins = mergedRehype;
|
|
38
|
+
if (mergedRecma) resolved.recmaPlugins = mergedRecma;
|
|
39
|
+
return resolved;
|
|
40
|
+
};
|
|
41
|
+
const createMdxLoaderPlugin = (compilerOptions, extensions) => {
|
|
42
|
+
const filter = createMdxExtensionFilter(extensions, { allowQueryString: true });
|
|
43
|
+
return {
|
|
44
|
+
name: "ecopages-jsx-mdx-loader",
|
|
45
|
+
setup(build) {
|
|
46
|
+
build.onLoad({ filter }, async (args) => {
|
|
47
|
+
const { compile } = await import("@mdx-js/mdx");
|
|
48
|
+
const filePath = args.path.includes("?") ? args.path.split("?")[0] : args.path;
|
|
49
|
+
const source = await readFile(filePath, "utf-8");
|
|
50
|
+
const compiled = await compile(new VFile({ value: source, path: filePath }), compilerOptions);
|
|
51
|
+
return {
|
|
52
|
+
contents: String(compiled.value),
|
|
53
|
+
loader: "js",
|
|
54
|
+
resolveDir: path.dirname(filePath)
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
const registerBunMdxPlugin = async (compilerOptions, extensions) => {
|
|
61
|
+
if (typeof Bun === "undefined") {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const filter = createMdxExtensionFilter(extensions);
|
|
65
|
+
Bun.plugin({
|
|
66
|
+
name: "ecopages-jsx-mdx",
|
|
67
|
+
setup(build) {
|
|
68
|
+
build.onLoad({ filter }, async (args) => {
|
|
69
|
+
const { compile } = await import("@mdx-js/mdx");
|
|
70
|
+
const source = await readFile(args.path, "utf-8");
|
|
71
|
+
const compiled = await compile(new VFile({ value: source, path: args.path }), compilerOptions);
|
|
72
|
+
return { contents: String(compiled.value), loader: "js" };
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
const isMdxFile = (filePath, extensions) => {
|
|
78
|
+
return extensions.some((ext) => filePath.endsWith(ext));
|
|
79
|
+
};
|
|
80
|
+
const normalizeMdxPageModule = (file, module) => {
|
|
81
|
+
if (typeof module.default !== "function") {
|
|
82
|
+
throw new TypeError("MDX file must export a callable default component.");
|
|
83
|
+
}
|
|
84
|
+
const Page = module.default;
|
|
85
|
+
const normalizedConfig = {
|
|
86
|
+
...module.config ?? Page.config ?? {},
|
|
87
|
+
...module.layout ? { layout: module.layout } : {},
|
|
88
|
+
__eco: module.config?.__eco ?? Page.config?.__eco ?? {
|
|
89
|
+
id: String(rapidhash(file)),
|
|
90
|
+
file,
|
|
91
|
+
integration: ECOPAGES_JSX_PLUGIN_NAME
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const wrappedPage = async (props) => await Page(props);
|
|
95
|
+
wrappedPage.config = normalizedConfig;
|
|
96
|
+
if (module.getMetadata ?? Page.metadata) {
|
|
97
|
+
wrappedPage.metadata = module.getMetadata ?? Page.metadata;
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
...module,
|
|
101
|
+
default: wrappedPage,
|
|
102
|
+
config: normalizedConfig
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
export {
|
|
106
|
+
appendMdxExtensions,
|
|
107
|
+
createMdxExtensionFilter,
|
|
108
|
+
createMdxLoaderPlugin,
|
|
109
|
+
isMdxFile,
|
|
110
|
+
normalizeMdxPageModule,
|
|
111
|
+
registerBunMdxPlugin,
|
|
112
|
+
resolveMdxCompilerOptions
|
|
113
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createMarkupNodeLike } from '@ecopages/jsx';
|
|
2
|
+
/**
|
|
3
|
+
* Owns optional Radiant SSR runtime installation for the JSX integration.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Radiant's server bridge and light-DOM shim are loaded lazily because most JSX
|
|
7
|
+
* renders do not need them. Once enabled, the resolved runtime modules are
|
|
8
|
+
* cached statically so repeated page and component renders do not keep paying
|
|
9
|
+
* module-resolution or shim-installation costs.
|
|
10
|
+
*/
|
|
11
|
+
export declare class EcopagesJsxRadiantSsrPolicy {
|
|
12
|
+
private static runtimeModules;
|
|
13
|
+
private static runtimeModulesPromise;
|
|
14
|
+
private readonly enabled;
|
|
15
|
+
constructor(enabled: boolean);
|
|
16
|
+
/**
|
|
17
|
+
* Ensures the Radiant SSR runtime is installed before a render needs it.
|
|
18
|
+
*/
|
|
19
|
+
prepareRuntime(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Runs one render inside Radiant's server runtime when the policy is enabled.
|
|
22
|
+
*/
|
|
23
|
+
withRuntime<T>(render: () => T): Promise<T>;
|
|
24
|
+
/**
|
|
25
|
+
* Converts one Radiant custom-element instance into trusted SSR markup.
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* The returned node-like wrapper lets the JSX server renderer preserve the
|
|
29
|
+
* generated host HTML without escaping it back into plain text.
|
|
30
|
+
*/
|
|
31
|
+
renderIntrinsicElementMarkup(instance: unknown): ReturnType<typeof createMarkupNodeLike> | undefined;
|
|
32
|
+
private ensureRuntimeInstalled;
|
|
33
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
+
import { createMarkupNodeLike } from "@ecopages/jsx";
|
|
4
|
+
import { isServerRenderHydrationActive } from "@ecopages/jsx/server";
|
|
5
|
+
class EcopagesJsxRadiantSsrPolicy {
|
|
6
|
+
static runtimeModules;
|
|
7
|
+
static runtimeModulesPromise;
|
|
8
|
+
enabled;
|
|
9
|
+
constructor(enabled) {
|
|
10
|
+
this.enabled = enabled;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Ensures the Radiant SSR runtime is installed before a render needs it.
|
|
14
|
+
*/
|
|
15
|
+
async prepareRuntime() {
|
|
16
|
+
if (!this.enabled) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
await this.ensureRuntimeInstalled();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Runs one render inside Radiant's server runtime when the policy is enabled.
|
|
23
|
+
*/
|
|
24
|
+
async withRuntime(render) {
|
|
25
|
+
if (!this.enabled) {
|
|
26
|
+
return render();
|
|
27
|
+
}
|
|
28
|
+
const runtimeModules = await EcopagesJsxRadiantSsrPolicy.runtimeModulesPromise;
|
|
29
|
+
if (!runtimeModules) {
|
|
30
|
+
return render();
|
|
31
|
+
}
|
|
32
|
+
return runtimeModules.withServerRadiantElementSsrRuntime(render);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Converts one Radiant custom-element instance into trusted SSR markup.
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* The returned node-like wrapper lets the JSX server renderer preserve the
|
|
39
|
+
* generated host HTML without escaping it back into plain text.
|
|
40
|
+
*/
|
|
41
|
+
renderIntrinsicElementMarkup(instance) {
|
|
42
|
+
const renderBridge = EcopagesJsxRadiantSsrPolicy.runtimeModules?.resolveRadiantElementRenderBridge(instance);
|
|
43
|
+
if (!renderBridge) {
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
return createMarkupNodeLike(
|
|
47
|
+
renderBridge.renderHostToString({
|
|
48
|
+
mode: isServerRenderHydrationActive() ? "hydrate" : "plain"
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
async ensureRuntimeInstalled() {
|
|
53
|
+
if (!EcopagesJsxRadiantSsrPolicy.runtimeModulesPromise) {
|
|
54
|
+
const radiantLightDomShimEntry = fileURLToPath(
|
|
55
|
+
import.meta.resolve("@ecopages/radiant/server/light-dom-shim")
|
|
56
|
+
);
|
|
57
|
+
const radiantPackageRoot = path.dirname(path.dirname(path.dirname(radiantLightDomShimEntry)));
|
|
58
|
+
const radiantElementSsrRuntimeModuleUrl = pathToFileURL(
|
|
59
|
+
path.join(radiantPackageRoot, "dist/server/radiant-element-ssr-bridge.js")
|
|
60
|
+
).href;
|
|
61
|
+
EcopagesJsxRadiantSsrPolicy.runtimeModulesPromise = Promise.all([
|
|
62
|
+
import(radiantElementSsrRuntimeModuleUrl),
|
|
63
|
+
import("@ecopages/radiant/server/light-dom-shim")
|
|
64
|
+
]).then(([radiantElementSsrRuntimeModule, lightDomShimModule2]) => {
|
|
65
|
+
const modules = {
|
|
66
|
+
installLightDomShim: lightDomShimModule2.installLightDomShim,
|
|
67
|
+
resolveRadiantElementRenderBridge: radiantElementSsrRuntimeModule.resolveRadiantElementRenderBridge,
|
|
68
|
+
withServerRadiantElementSsrRuntime: radiantElementSsrRuntimeModule.withServerRadiantElementSsrRuntime
|
|
69
|
+
};
|
|
70
|
+
EcopagesJsxRadiantSsrPolicy.runtimeModules = modules;
|
|
71
|
+
return modules;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const lightDomShimModule = await EcopagesJsxRadiantSsrPolicy.runtimeModulesPromise;
|
|
75
|
+
lightDomShimModule.installLightDomShim();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
EcopagesJsxRadiantSsrPolicy
|
|
80
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
|
|
2
|
+
export declare const ECOPAGES_JSX_SSR_RENDER_STATE_KEY: unique symbol;
|
|
3
|
+
/**
|
|
4
|
+
* Tracks JSX SSR asset-collection 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
|
+
private readonly dedupeProcessedAssets;
|
|
15
|
+
constructor(dedupeProcessedAssets: (assets: ProcessedAsset[]) => ProcessedAsset[]);
|
|
16
|
+
/**
|
|
17
|
+
* Runs one render inside the active session scope.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* When a render is already active, the current session state is reused and
|
|
21
|
+
* mirrored back into the JSX SSR scope so nested `renderToString()` calls see
|
|
22
|
+
* the same asset frame stack. When no session exists yet, a new state is
|
|
23
|
+
* created and published through both the internal async-local store and the
|
|
24
|
+
* JSX SSR scope bridge.
|
|
25
|
+
*/
|
|
26
|
+
withActiveScope<T>(render: () => T): T;
|
|
27
|
+
beginCollectedAssetFrame(): ProcessedAsset[];
|
|
28
|
+
endCollectedAssetFrame(frame: ProcessedAsset[]): ProcessedAsset[];
|
|
29
|
+
recordCollectedAssets(collectedAssets: ProcessedAsset[]): ProcessedAsset[];
|
|
30
|
+
/**
|
|
31
|
+
* Resolves the current render-session state from the local async scope first.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* The fallback read from `@ecopages/jsx/server` keeps compatibility with code
|
|
35
|
+
* that still inspects the public SSR scope directly, while the async-local
|
|
36
|
+
* store remains the authoritative source for renderer-owned bookkeeping.
|
|
37
|
+
*/
|
|
38
|
+
private getState;
|
|
39
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { getActiveSsrScopeValue, withActiveSsrScopeValue } from "@ecopages/jsx/server";
|
|
3
|
+
const ECOPAGES_JSX_SSR_RENDER_STATE_KEY = /* @__PURE__ */ Symbol.for("@ecopages/ecopages-jsx.ssr-render-state");
|
|
4
|
+
const renderStateStorage = new AsyncLocalStorage();
|
|
5
|
+
class EcopagesJsxRenderSession {
|
|
6
|
+
dedupeProcessedAssets;
|
|
7
|
+
constructor(dedupeProcessedAssets) {
|
|
8
|
+
this.dedupeProcessedAssets = dedupeProcessedAssets;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Runs one render inside the active session scope.
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* When a render is already active, the current session state is reused and
|
|
15
|
+
* mirrored back into the JSX SSR scope so nested `renderToString()` calls see
|
|
16
|
+
* the same asset frame stack. When no session exists yet, a new state is
|
|
17
|
+
* created and published through both the internal async-local store and the
|
|
18
|
+
* JSX SSR scope bridge.
|
|
19
|
+
*/
|
|
20
|
+
withActiveScope(render) {
|
|
21
|
+
const activeState = renderStateStorage.getStore();
|
|
22
|
+
if (activeState) {
|
|
23
|
+
return withActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY, activeState, render);
|
|
24
|
+
}
|
|
25
|
+
const jsxScopeState = getActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY);
|
|
26
|
+
if (jsxScopeState) {
|
|
27
|
+
return renderStateStorage.run(jsxScopeState, () => render());
|
|
28
|
+
}
|
|
29
|
+
const state = {
|
|
30
|
+
collectedAssetFrames: []
|
|
31
|
+
};
|
|
32
|
+
return renderStateStorage.run(
|
|
33
|
+
state,
|
|
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);
|
|
47
|
+
}
|
|
48
|
+
return this.dedupeProcessedAssets(activeFrame);
|
|
49
|
+
}
|
|
50
|
+
recordCollectedAssets(collectedAssets) {
|
|
51
|
+
const dedupedAssets = this.dedupeProcessedAssets(collectedAssets);
|
|
52
|
+
const state = this.getState();
|
|
53
|
+
const activeFrame = state.collectedAssetFrames[state.collectedAssetFrames.length - 1];
|
|
54
|
+
if (activeFrame) {
|
|
55
|
+
activeFrame.push(...dedupedAssets);
|
|
56
|
+
}
|
|
57
|
+
return dedupedAssets;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolves the current render-session state from the local async scope first.
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* The fallback read from `@ecopages/jsx/server` keeps compatibility with code
|
|
64
|
+
* that still inspects the public SSR scope directly, while the async-local
|
|
65
|
+
* store remains the authoritative source for renderer-owned bookkeeping.
|
|
66
|
+
*/
|
|
67
|
+
getState() {
|
|
68
|
+
const state = renderStateStorage.getStore() ?? getActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY);
|
|
69
|
+
if (!state) {
|
|
70
|
+
throw new Error("Ecopages JSX SSR render state is unavailable outside the active render scope.");
|
|
71
|
+
}
|
|
72
|
+
return state;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
ECOPAGES_JSX_SSR_RENDER_STATE_KEY,
|
|
77
|
+
EcopagesJsxRenderSession
|
|
78
|
+
};
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import type { ComponentRenderInput, ComponentRenderResult, EcoComponent,
|
|
1
|
+
import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, IntegrationRendererRenderOptions, RouteRendererBody } from '@ecopages/core';
|
|
2
2
|
import { IntegrationRenderer, type RenderToResponseContext, type RouteModuleLoadOptions } from '@ecopages/core/route-renderer/integration-renderer';
|
|
3
3
|
import { type JsxRenderable } from '@ecopages/jsx';
|
|
4
|
+
import { type EcopagesJsxMdxPageModule } from './ecopages-jsx-mdx.js';
|
|
4
5
|
import type { EcopagesJsxRendererOptions } from './ecopages-jsx.types.js';
|
|
5
6
|
export type { EcopagesJsxRendererConfig, EcopagesJsxRendererOptions } from './ecopages-jsx.types.js';
|
|
6
|
-
type MdxPageModule = EcoPageFile<{
|
|
7
|
-
config?: EcoComponentConfig;
|
|
8
|
-
layout?: EcoComponent;
|
|
9
|
-
getMetadata?: GetMetadata;
|
|
10
|
-
}>;
|
|
11
7
|
/**
|
|
12
8
|
* Local Ecopages renderer for JSX templates in the docs app.
|
|
13
9
|
*
|
|
@@ -16,68 +12,29 @@ type MdxPageModule = EcoPageFile<{
|
|
|
16
12
|
*/
|
|
17
13
|
export declare class EcopagesJsxRenderer extends IntegrationRenderer<JsxRenderable> {
|
|
18
14
|
name: string;
|
|
19
|
-
private static radiantServerRuntimeInstallPromise;
|
|
20
|
-
private static readonly SCRIPT_IMPORT_RE;
|
|
21
|
-
private readonly intrinsicCustomElementAssets;
|
|
22
|
-
private readonly intrinsicCustomElementScriptFiles;
|
|
23
|
-
private collectedAssetFrames;
|
|
24
|
-
private importedIntrinsicScriptFrames;
|
|
25
15
|
private readonly mdxExtensions;
|
|
26
|
-
private readonly
|
|
16
|
+
private readonly renderSession;
|
|
17
|
+
private readonly radiantSsrPolicy;
|
|
27
18
|
/**
|
|
28
19
|
* Re-renders queued JSX children inside the owning renderer so nested custom
|
|
29
|
-
* elements and queued foreign
|
|
20
|
+
* elements and queued foreign subtrees contribute assets to the same frame.
|
|
30
21
|
*/
|
|
31
|
-
private
|
|
22
|
+
private renderQueuedForeignSubtreeChildren;
|
|
32
23
|
/**
|
|
33
|
-
* Resolves queued foreign
|
|
24
|
+
* Resolves queued foreign subtrees after JSX has been stringified.
|
|
34
25
|
*
|
|
35
|
-
* JSX content needs one extra render pass because child
|
|
26
|
+
* JSX content needs one extra render pass because child foreign subtrees may emit
|
|
36
27
|
* additional browser assets while also replacing placeholder tokens.
|
|
37
28
|
*/
|
|
38
|
-
private
|
|
29
|
+
private resolveOwnedForeignSubtreeHtml;
|
|
39
30
|
constructor({ appConfig, assetProcessingService, resolvedIntegrationDependencies, jsxConfig, runtimeOrigin, }: EcopagesJsxRendererOptions);
|
|
40
31
|
/** Returns whether the requested page file should be treated as MDX. */
|
|
41
32
|
isMdxFile(filePath: string): boolean;
|
|
42
|
-
protected importPageFile(file: string, options?: RouteModuleLoadOptions): Promise<
|
|
33
|
+
protected importPageFile(file: string, options?: RouteModuleLoadOptions): Promise<EcopagesJsxMdxPageModule>;
|
|
43
34
|
render(options: IntegrationRendererRenderOptions<JsxRenderable>): Promise<RouteRendererBody>;
|
|
44
35
|
renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult>;
|
|
45
|
-
protected createComponentBoundaryRuntime(options: {
|
|
46
|
-
boundaryInput: ComponentRenderInput;
|
|
47
|
-
rendererCache: Map<string, IntegrationRenderer<any>>;
|
|
48
|
-
}): import("@ecopages/core").ComponentBoundaryRuntime;
|
|
49
36
|
renderToResponse<P = any>(view: EcoComponent<P>, props: P, ctx: RenderToResponseContext): Promise<Response>;
|
|
50
|
-
/**
|
|
51
|
-
* Normalizes MDX modules into the same page contract as JSX route modules.
|
|
52
|
-
*
|
|
53
|
-
* MDX files export page metadata alongside generated component code, so the
|
|
54
|
-
* renderer folds those exports back into the Ecopages component shape before
|
|
55
|
-
* any layout or document-shell logic runs.
|
|
56
|
-
*/
|
|
57
|
-
private normalizeMdxPageModule;
|
|
58
|
-
private beginCollectedAssetFrame;
|
|
59
|
-
private endCollectedAssetFrame;
|
|
60
37
|
private renderJsx;
|
|
61
|
-
private
|
|
62
|
-
private recordCollectedAssets;
|
|
63
|
-
private beginImportedIntrinsicScriptFrame;
|
|
64
|
-
private endImportedIntrinsicScriptFrame;
|
|
65
|
-
private getActiveImportedIntrinsicScriptFiles;
|
|
66
|
-
/**
|
|
67
|
-
* Collects intrinsic custom-element script files already owned by the current
|
|
68
|
-
* component tree through direct source imports or dependency declarations.
|
|
69
|
-
*/
|
|
70
|
-
private collectImportedIntrinsicScriptFiles;
|
|
71
|
-
private extractImportedIntrinsicScriptFiles;
|
|
72
|
-
private extractConfiguredDependencyScriptFiles;
|
|
73
|
-
private resolveImportedIntrinsicScriptFile;
|
|
74
|
-
private ensureRadiantServerRuntimeIfEnabled;
|
|
75
|
-
private ensureRadiantServerRuntimeInstalled;
|
|
76
|
-
private isFunctionComponent;
|
|
77
|
-
private createComponentProps;
|
|
78
|
-
private collectComponentAssets;
|
|
79
|
-
private invokeComponent;
|
|
80
|
-
private createEcoMeta;
|
|
81
|
-
private wrapMdxPage;
|
|
38
|
+
private withCustomElementRenderHook;
|
|
82
39
|
private createIntrinsicCustomElementRenderHook;
|
|
83
40
|
}
|