@ecopages/ecopages-jsx 0.2.0-beta.4 → 0.2.0-beta.41

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 CHANGED
@@ -48,6 +48,7 @@ Radiant support is enabled by default. When `radiant: true`, the plugin keeps th
48
48
 
49
49
  - Ecopages JSX owns page-level JSX SSR and container hydration.
50
50
  - Radiant SSR is activated on the server through `@ecopages/radiant/server/render-component`.
51
+ - The server reuses Radiant's complete light-DOM surface, replacing partial globals before Radiant elements are imported.
51
52
  - Radiant host hydration is activated on the client through an explicit head bootstrap that imports `@ecopages/radiant/client/install-hydrator` before intrinsic custom-element modules load.
52
53
 
53
54
  That means server-rendered `RadiantElement` hosts hydrate in place only when both the SSR markers and the explicit client hydrator are present. Without the client hydrator, Radiant intentionally falls back to a fresh client render on first connect.
@@ -99,6 +100,7 @@ Ecopages JSX can own the outer page shell or just a nested foreign subtree. When
99
100
 
100
101
  Important:
101
102
 
103
+ - Cross-integration JSX children must use `EcoEmbed`. Opaque plain objects fail fast at core foreign-child queue boundaries; they are not coerced with `String(object)`.
102
104
  - Components that may render foreign children must declare those children in `config.dependencies.components`.
103
105
  - Ecopages validates mixed-renderer ownership from declared dependencies during render preparation. It does not treat rendered HTML alone as the source of truth.
104
106
  - Ecopages JSX keeps raw-markup preservation and asset collection inside the JSX renderer.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/ecopages-jsx",
3
- "version": "0.2.0-beta.4",
3
+ "version": "0.2.0-beta.41",
4
4
  "description": "JSX integration plugin for Ecopages",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -27,16 +27,17 @@
27
27
  },
28
28
  "repository": {
29
29
  "type": "git",
30
- "url": "https://github.com/ecopages/ecopages.git",
30
+ "url": "git+https://github.com/ecopages/ecopages.git",
31
31
  "directory": "packages/integrations/ecopages-jsx"
32
32
  },
33
33
  "dependencies": {
34
+ "@ecopages/mdx": "0.2.0-beta.41",
34
35
  "@mdx-js/mdx": "^3.1.1",
35
36
  "vfile": "^6.0.3"
36
37
  },
37
38
  "peerDependencies": {
38
- "@ecopages/core": "0.2.0-beta.4",
39
- "@ecopages/jsx": "0.3.0-alpha.25",
40
- "@ecopages/radiant": "0.3.0-alpha.25"
39
+ "@ecopages/core": "0.2.0-beta.41",
40
+ "@ecopages/jsx": "0.3.0-beta.6",
41
+ "@ecopages/radiant": "0.3.0-beta.6"
41
42
  }
42
43
  }
@@ -0,0 +1,17 @@
1
+ import type { EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
2
+ import type { DevTransformBundleContributor } from '@ecopages/core/dev/transform-server';
3
+ import type { EcopagesJsxHmrStrategy } from '../ecopages-jsx-hmr-strategy.js';
4
+ export type EcopagesJsxDevTransformContributorOptions = {
5
+ strategy: EcopagesJsxHmrStrategy;
6
+ getMdxLoaderPlugin: () => EcoBuildPlugin | undefined;
7
+ };
8
+ /**
9
+ * Supplies Ecopages JSX module plugins for the core dev transform bundler.
10
+ */
11
+ export declare class EcopagesJsxDevTransformContributor implements DevTransformBundleContributor {
12
+ private readonly strategy;
13
+ private readonly getMdxLoaderPlugin;
14
+ constructor(options: EcopagesJsxDevTransformContributorOptions);
15
+ ownsModule(sourcePath: string): boolean;
16
+ getModulePlugins(sourcePath: string): Promise<readonly EcoBuildPlugin[]>;
17
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Supplies Ecopages JSX module plugins for the core dev transform bundler.
3
+ */
4
+ export class EcopagesJsxDevTransformContributor {
5
+ strategy;
6
+ getMdxLoaderPlugin;
7
+ constructor(options) {
8
+ this.strategy = options.strategy;
9
+ this.getMdxLoaderPlugin = options.getMdxLoaderPlugin;
10
+ }
11
+ ownsModule(sourcePath) {
12
+ return this.strategy.ownsDevTransformEntrypoint(sourcePath);
13
+ }
14
+ async getModulePlugins(sourcePath) {
15
+ if (!sourcePath.endsWith('.mdx')) {
16
+ return [];
17
+ }
18
+ const mdxLoader = this.getMdxLoaderPlugin();
19
+ return mdxLoader ? [mdxLoader] : [];
20
+ }
21
+ }
package/src/eco-embed.js CHANGED
@@ -1,11 +1,9 @@
1
- import { eco } from "@ecopages/core";
2
- function EcoEmbed({
3
- component,
4
- props,
5
- children
6
- }) {
7
- return eco.embed(component, props, children);
1
+ /** @jsxImportSource @ecopages/jsx */
2
+ import { eco } from '@ecopages/core';
3
+ /**
4
+ * Renders a foreign or same-integration eco component from an Ecopages JSX
5
+ * file without forcing inline mixed-JSX authoring.
6
+ */
7
+ export function EcoEmbed({ component, props, children, }) {
8
+ return eco.embed(component, props, children);
8
9
  }
9
- export {
10
- EcoEmbed
11
- };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Tracks the source files that participate in the active SSR render tree.
3
+ *
4
+ * @remarks
5
+ * The JSX integration renderer calls {@link updateEjsxHmrOwnership} after each
6
+ * page, component, or view render so the HMR strategy can answer
7
+ * "does this changed file affect SSR HTML?" without re-walking the tree on
8
+ * every watcher event.
9
+ *
10
+ * The state is module-scoped because the HMR strategy and renderer live in
11
+ * different call sites (server strategy vs SSR renderer) and share no other
12
+ * lifecycle anchor. A fresh state is installed only when the source-file set
13
+ * changes, so the renderer's per-render hook stays O(1) in the steady state.
14
+ */
15
+ import type { EcoComponent } from '@ecopages/core';
16
+ export type EjsxHmrOwnershipState = {
17
+ fileOwners: ReadonlySet<string>;
18
+ hash: string;
19
+ };
20
+ /**
21
+ * Returns the most recently recorded active render tree.
22
+ */
23
+ export declare function getEjsxHmrOwnership(): EjsxHmrOwnershipState;
24
+ /**
25
+ * Updates the active render tree from one or more root components.
26
+ *
27
+ * @remarks
28
+ * Walks each root through `getComponentIdentity`, `config.dependencies.components[*].config`,
29
+ * and `config.layouts[*].config` recursively, collecting every file path. The
30
+ * state is replaced only when the resulting file set has a different hash, so
31
+ * the renderer's per-render hook is free in the steady state.
32
+ */
33
+ export declare function updateEjsxHmrOwnership(components: ReadonlyArray<EcoComponent | Partial<EcoComponent> | undefined>): void;
34
+ /**
35
+ * Merges one render tree into a pending ownership set during an active render.
36
+ */
37
+ export declare function mergeEjsxHmrOwnership(target: Set<string>, components: ReadonlyArray<EcoComponent | undefined>): void;
38
+ /**
39
+ * Publishes the merged ownership set for the completed render.
40
+ */
41
+ export declare function publishEjsxHmrOwnership(files: ReadonlySet<string>): void;
42
+ /**
43
+ * Resets the state to empty. Used by tests and by HMR manager teardown.
44
+ */
45
+ export declare function resetEjsxHmrOwnership(): void;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Tracks the source files that participate in the active SSR render tree.
3
+ *
4
+ * @remarks
5
+ * The JSX integration renderer calls {@link updateEjsxHmrOwnership} after each
6
+ * page, component, or view render so the HMR strategy can answer
7
+ * "does this changed file affect SSR HTML?" without re-walking the tree on
8
+ * every watcher event.
9
+ *
10
+ * The state is module-scoped because the HMR strategy and renderer live in
11
+ * different call sites (server strategy vs SSR renderer) and share no other
12
+ * lifecycle anchor. A fresh state is installed only when the source-file set
13
+ * changes, so the renderer's per-render hook stays O(1) in the steady state.
14
+ */
15
+ import { rapidhash } from '@ecopages/core/hash';
16
+ import { collectComponentConfigFilePaths } from '@ecopages/core/route-renderer/page-loading/file-scoped-dependency-components';
17
+ const EMPTY_STATE = {
18
+ fileOwners: new Set(),
19
+ hash: '',
20
+ };
21
+ let currentState = EMPTY_STATE;
22
+ /**
23
+ * Returns the most recently recorded active render tree.
24
+ */
25
+ export function getEjsxHmrOwnership() {
26
+ return currentState;
27
+ }
28
+ /**
29
+ * Updates the active render tree from one or more root components.
30
+ *
31
+ * @remarks
32
+ * Walks each root through `getComponentIdentity`, `config.dependencies.components[*].config`,
33
+ * and `config.layouts[*].config` recursively, collecting every file path. The
34
+ * state is replaced only when the resulting file set has a different hash, so
35
+ * the renderer's per-render hook is free in the steady state.
36
+ */
37
+ export function updateEjsxHmrOwnership(components) {
38
+ publishEjsxHmrOwnership(collectFileOwners(components));
39
+ }
40
+ /**
41
+ * Merges one render tree into a pending ownership set during an active render.
42
+ */
43
+ export function mergeEjsxHmrOwnership(target, components) {
44
+ for (const file of collectFileOwners(components)) {
45
+ target.add(file);
46
+ }
47
+ }
48
+ /**
49
+ * Publishes the merged ownership set for the completed render.
50
+ */
51
+ export function publishEjsxHmrOwnership(files) {
52
+ const hash = hashFileSet(files);
53
+ if (hash === currentState.hash) {
54
+ return;
55
+ }
56
+ currentState = {
57
+ fileOwners: new Set(files),
58
+ hash,
59
+ };
60
+ }
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
+ function collectFileOwners(components) {
68
+ return collectComponentConfigFilePaths(components, { includeLayouts: true });
69
+ }
70
+ function hashFileSet(files) {
71
+ if (files.size === 0) {
72
+ return '';
73
+ }
74
+ const sorted = Array.from(files).sort();
75
+ const joined = sorted.join('\n');
76
+ return rapidhash(joined).toString(36);
77
+ }
@@ -0,0 +1,19 @@
1
+ import { HmrStrategy, type HmrAction } from '@ecopages/core/hmr/hmr-strategy';
2
+ import type { HmrRegisteredEntrypointsContext } from '@ecopages/core/hmr/hmr-registered-entrypoints-context';
3
+ export type EcopagesJsxHmrStrategyContext = HmrRegisteredEntrypointsContext & {
4
+ getSrcDir(): string;
5
+ getPagesDir(): string;
6
+ getLayoutsDir(): string;
7
+ getIncludesDir(): string;
8
+ getTemplateExtensions(): ReadonlyArray<string>;
9
+ };
10
+ export declare class EcopagesJsxHmrStrategy extends HmrStrategy {
11
+ readonly type: 100;
12
+ private readonly context;
13
+ constructor(context: EcopagesJsxHmrStrategyContext);
14
+ matches(filePath: string): boolean;
15
+ ownsDevTransformEntrypoint(filePath: string): boolean;
16
+ process(filePath: string): Promise<HmrAction>;
17
+ private isPageOrLayoutSource;
18
+ private hasJsxTemplateExtension;
19
+ }
@@ -0,0 +1,67 @@
1
+ import path from 'node:path';
2
+ import { HmrStrategy, HmrStrategyType } from '@ecopages/core/hmr/hmr-strategy';
3
+ import { isRegisteredDevTransformEntrypoint } from '@ecopages/core/hmr/hmr-entrypoint-output';
4
+ import { getEjsxHmrOwnership } from './ecopages-jsx-hmr-ownership.js';
5
+ export class EcopagesJsxHmrStrategy extends HmrStrategy {
6
+ type = HmrStrategyType.INTEGRATION;
7
+ context;
8
+ constructor(context) {
9
+ super();
10
+ this.context = context;
11
+ }
12
+ matches(filePath) {
13
+ const resolvedPath = path.resolve(filePath);
14
+ if (isRegisteredDevTransformEntrypoint(this.context.getRegisteredEntrypoints(), resolvedPath)) {
15
+ return false;
16
+ }
17
+ const srcDir = path.resolve(this.context.getSrcDir());
18
+ if (!resolvedPath.startsWith(`${srcDir}${path.sep}`) && resolvedPath !== srcDir) {
19
+ return false;
20
+ }
21
+ const includesDir = path.resolve(this.context.getIncludesDir());
22
+ if (includesDir && (resolvedPath === includesDir || resolvedPath.startsWith(`${includesDir}${path.sep}`))) {
23
+ return false;
24
+ }
25
+ if (this.isPageOrLayoutSource(resolvedPath)) {
26
+ return this.hasJsxTemplateExtension(resolvedPath);
27
+ }
28
+ const ownership = getEjsxHmrOwnership();
29
+ if (ownership.fileOwners.has(resolvedPath)) {
30
+ return true;
31
+ }
32
+ return false;
33
+ }
34
+ ownsDevTransformEntrypoint(filePath) {
35
+ const resolvedPath = path.resolve(filePath);
36
+ if (!isRegisteredDevTransformEntrypoint(this.context.getRegisteredEntrypoints(), resolvedPath)) {
37
+ return false;
38
+ }
39
+ return this.isPageOrLayoutSource(resolvedPath) && this.hasJsxTemplateExtension(resolvedPath);
40
+ }
41
+ async process(filePath) {
42
+ return {
43
+ type: 'broadcast',
44
+ events: [
45
+ {
46
+ type: 'layout-update',
47
+ path: filePath,
48
+ timestamp: Date.now(),
49
+ },
50
+ ],
51
+ };
52
+ }
53
+ isPageOrLayoutSource(resolvedPath) {
54
+ const pagesDir = path.resolve(this.context.getPagesDir());
55
+ const layoutsDir = path.resolve(this.context.getLayoutsDir());
56
+ if (pagesDir && (resolvedPath === pagesDir || resolvedPath.startsWith(`${pagesDir}${path.sep}`))) {
57
+ return true;
58
+ }
59
+ if (layoutsDir && (resolvedPath === layoutsDir || resolvedPath.startsWith(`${layoutsDir}${path.sep}`))) {
60
+ return true;
61
+ }
62
+ return false;
63
+ }
64
+ hasJsxTemplateExtension(resolvedPath) {
65
+ return this.context.getTemplateExtensions().some((extension) => resolvedPath.endsWith(extension));
66
+ }
67
+ }
@@ -1,6 +1,7 @@
1
1
  import type { CompileOptions } from '@mdx-js/mdx';
2
2
  import type { EcoComponent, EcoComponentConfig, EcoFunctionComponent, EcoPageFile, GetMetadata } from '@ecopages/core';
3
3
  import type { EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
4
+ import { appendMdxExtensions, createMdxExtensionFilter } from '@ecopages/mdx/core';
4
5
  import type { JsxRenderable } from '@ecopages/jsx';
5
6
  import type { EcopagesJsxMdxCompileOptions, EcopagesJsxMdxOptions } from './ecopages-jsx.types.js';
6
7
  export type ResolvedMdxCompileOptions = EcopagesJsxMdxCompileOptions & Pick<CompileOptions, 'jsxImportSource' | 'jsxRuntime'>;
@@ -10,10 +11,7 @@ export type EcopagesJsxMdxPageModule = EcoPageFile<{
10
11
  layout?: EcoComponent;
11
12
  getMetadata?: GetMetadata;
12
13
  }>;
13
- export declare const createMdxExtensionFilter: (extensions: string[], options?: {
14
- allowQueryString?: boolean;
15
- }) => RegExp;
16
- export declare const appendMdxExtensions: (target: string[], mdxExtensions: string[]) => void;
14
+ export { appendMdxExtensions, createMdxExtensionFilter };
17
15
  export declare const resolveMdxCompilerOptions: (mdxOptions: EcopagesJsxMdxOptions) => ResolvedMdxCompileOptions;
18
16
  export declare const createMdxLoaderPlugin: (compilerOptions: ResolvedMdxCompileOptions, extensions: string[]) => EcoBuildPlugin;
19
17
  export declare const registerBunMdxPlugin: (compilerOptions: ResolvedMdxCompileOptions, extensions: string[]) => Promise<void>;
@@ -1,113 +1,70 @@
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);
1
+ import { readFile } from 'node:fs/promises';
2
+ import { ensurePageConfigLayouts } from '@ecopages/core/eco/page-layout-normalization';
3
+ import { rapidhash } from '@ecopages/core/hash';
4
+ import { appendMdxExtensions, createMdxExtensionFilter, createMdxLoaderPlugin as createMdxLoaderPluginCore, resolveMdxCompilerOptions as resolveMdxCompilerOptionsCore, } from '@ecopages/mdx/core';
5
+ import { VFile } from 'vfile';
6
+ import { ECOPAGES_JSX_PLUGIN_NAME } from './ecopages-jsx.constants.js';
7
+ export { appendMdxExtensions, createMdxExtensionFilter };
8
+ export const resolveMdxCompilerOptions = (mdxOptions) => resolveMdxCompilerOptionsCore(mdxOptions, {
9
+ jsxImportSource: '@ecopages/jsx',
10
+ defaults: {
11
+ format: 'detect',
12
+ outputFormat: 'program',
13
+ },
14
+ });
15
+ export const createMdxLoaderPlugin = (compilerOptions, extensions) => createMdxLoaderPluginCore({
16
+ name: 'ecopages-jsx-mdx-loader',
17
+ compilerOptions,
18
+ extensions,
19
+ includeSourceMap: false,
20
+ loader: 'js',
21
+ });
22
+ export const registerBunMdxPlugin = async (compilerOptions, extensions) => {
23
+ if (typeof Bun === 'undefined') {
24
+ return;
20
25
  }
21
- }
26
+ const filter = createMdxExtensionFilter(extensions);
27
+ Bun.plugin({
28
+ name: 'ecopages-jsx-mdx',
29
+ setup(build) {
30
+ build.onLoad({ filter }, async (args) => {
31
+ const { compile } = await import('@mdx-js/mdx');
32
+ const source = await readFile(args.path, 'utf-8');
33
+ const compiled = await compile(new VFile({ value: source, path: args.path }), compilerOptions);
34
+ return { contents: String(compiled.value), loader: 'js' };
35
+ });
36
+ },
37
+ });
22
38
  };
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;
39
+ export const isMdxFile = (filePath, extensions) => {
40
+ return extensions.some((ext) => filePath.endsWith(ext));
40
41
  };
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
- });
42
+ export const normalizeMdxPageModule = (file, module) => {
43
+ if (typeof module.default !== 'function') {
44
+ throw new TypeError('MDX file must export a callable default component.');
57
45
  }
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
- });
46
+ const Page = module.default;
47
+ const normalizedConfig = {
48
+ ...(module.config ?? Page.config ?? {}),
49
+ identity: module.config?.identity ??
50
+ Page.config?.identity ?? {
51
+ id: String(rapidhash(file)),
52
+ file,
53
+ integration: ECOPAGES_JSX_PLUGIN_NAME,
54
+ },
55
+ };
56
+ if (module.layout && !(normalizedConfig.layouts && normalizedConfig.layouts.length > 0)) {
57
+ normalizedConfig.layout = module.layout;
74
58
  }
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
59
+ ensurePageConfigLayouts(normalizedConfig);
60
+ const wrappedPage = async (props) => await Page(props);
61
+ wrappedPage.config = normalizedConfig;
62
+ if (module.getMetadata ?? Page.metadata) {
63
+ wrappedPage.metadata = module.getMetadata ?? Page.metadata;
92
64
  }
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
65
+ return {
66
+ ...module,
67
+ default: wrappedPage,
68
+ config: normalizedConfig,
69
+ };
113
70
  };
@@ -4,13 +4,15 @@ import { createMarkupNodeLike } from '@ecopages/jsx';
4
4
  *
5
5
  * @remarks
6
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.
7
+ * renders do not need them. Resolved runtime modules are cached on the policy
8
+ * instance so repeated renders on the same renderer reuse one load without
9
+ * process-wide mutable class state. Light-DOM constructor installation remains
10
+ * process-global and idempotent via {@link ensureRadiantLightDomGlobals}; the
11
+ * Radiant installer also repairs incomplete DOM globals left by another runtime.
10
12
  */
11
13
  export declare class EcopagesJsxRadiantSsrPolicy {
12
- private static runtimeModules;
13
- private static runtimeModulesPromise;
14
+ private runtimeModules;
15
+ private runtimeModulesPromise;
14
16
  private readonly enabled;
15
17
  constructor(enabled: boolean);
16
18
  /**