@ecopages/ecopages-jsx 0.2.0-alpha.13 → 0.2.0-alpha.15

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 CHANGED
@@ -8,8 +8,12 @@
8
8
 
9
9
  ### Bug Fixes
10
10
 
11
- - Fixed mixed-renderer JSX boundary resolution, Radiant/browser runtime asset mapping, required peer dependency wiring, Bun-only MDX loader setup, and multi-dot MDX extension matching.
11
+ - Fixed Radiant SSR page inspection to install the light-DOM shim before JSX page modules are imported outside the normal render pass.
12
+ - Restored direct `EcopagesJsxPlugin` construction so the exported class still accepts the public plugin options shape.
13
+ - Fixed intrinsic custom-element asset discovery so Ecopages JSX registers scripts declared with decorator and function-call `customElement(...)` syntax.
14
+ - Fixed the Ecopages JSX browser runtime import map so browser builds no longer expose `@ecopages/jsx/server` through the shared JSX vendor bundle.
15
+ - Fixed the Ecopages JSX browser runtime bundle so Radiant custom-element scripts no longer fail on a duplicate `jsxDEV` export cycle.
12
16
 
13
- ### Documentation
17
+ ### Refactoring
14
18
 
15
- - Updated the README to document JSX route extensions, Radiant defaults, MDX configuration, and mixed-renderer boundary ownership.
19
+ - 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/ecopages-jsx",
3
- "version": "0.2.0-alpha.13",
3
+ "version": "0.2.0-alpha.15",
4
4
  "description": "JSX integration plugin for Ecopages",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -21,7 +21,7 @@
21
21
  "vfile": "^6.0.3"
22
22
  },
23
23
  "peerDependencies": {
24
- "@ecopages/core": "0.2.0-alpha.13",
24
+ "@ecopages/core": "0.2.0-alpha.15",
25
25
  "@ecopages/jsx": "^0.3.0-alpha.0",
26
26
  "@ecopages/radiant": "^0.3.0-alpha.0"
27
27
  }
@@ -1,8 +1,8 @@
1
1
  import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoComponentConfig, EcoPageFile, GetMetadata, IntegrationRendererRenderOptions, RouteRendererBody } from '@ecopages/core';
2
- import type { EcoPagesAppConfig } from '@ecopages/core/internal-types';
3
- import { IntegrationRenderer, type RenderToResponseContext } from '@ecopages/core/route-renderer/integration-renderer';
4
- import type { AssetProcessingService, ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
2
+ import { IntegrationRenderer, type RenderToResponseContext, type RouteModuleLoadOptions } from '@ecopages/core/route-renderer/integration-renderer';
5
3
  import { type JsxRenderable } from '@ecopages/jsx';
4
+ import type { EcopagesJsxRendererOptions } from './ecopages-jsx.types.js';
5
+ export type { EcopagesJsxRendererConfig, EcopagesJsxRendererOptions } from './ecopages-jsx.types.js';
6
6
  type MdxPageModule = EcoPageFile<{
7
7
  config?: EcoComponentConfig;
8
8
  layout?: EcoComponent;
@@ -16,28 +16,27 @@ type MdxPageModule = EcoPageFile<{
16
16
  */
17
17
  export declare class EcopagesJsxRenderer extends IntegrationRenderer<JsxRenderable> {
18
18
  name: string;
19
- static mdxExtensions: string[];
20
- private intrinsicCustomElementAssets;
19
+ private static radiantLightDomShimInstallPromise;
20
+ private readonly intrinsicCustomElementAssets;
21
21
  private collectedAssetFrames;
22
- private radiantSsrEnabled;
23
- private resolveQueuedBoundaryChildren;
24
- private resolveQueuedBoundaryHtml;
25
- constructor({ appConfig, assetProcessingService, resolvedIntegrationDependencies, runtimeOrigin, }: {
26
- appConfig: EcoPagesAppConfig;
27
- assetProcessingService: AssetProcessingService;
28
- resolvedIntegrationDependencies: ProcessedAsset[];
29
- runtimeOrigin: string;
30
- });
31
- /** Returns whether the requested page file should be treated as MDX. */
32
- isMdxFile(filePath: string): boolean;
22
+ private readonly mdxExtensions;
23
+ private readonly radiantSsrEnabled;
24
+ /**
25
+ * Re-renders queued JSX children inside the owning renderer so nested custom
26
+ * elements and queued foreign boundaries contribute assets to the same frame.
27
+ */
28
+ private renderQueuedBoundaryChildren;
33
29
  /**
34
- * Supplies the intrinsic custom-element assets discovered by the plugin so
35
- * component renders can attach the correct client scripts.
30
+ * Resolves queued foreign boundaries after JSX has been stringified.
31
+ *
32
+ * JSX content needs one extra render pass because child boundaries may emit
33
+ * additional browser assets while also replacing placeholder tokens.
36
34
  */
37
- setIntrinsicCustomElementAssets(assetsByTagName: Map<string, readonly ProcessedAsset[]>): void;
38
- /** Enables the Radiant SSR light-DOM shim for this renderer instance. */
39
- setRadiantSsrEnabled(enabled: boolean): void;
40
- protected importPageFile(file: string): Promise<MdxPageModule>;
35
+ private resolveOwnedBoundaryHtml;
36
+ constructor({ appConfig, assetProcessingService, resolvedIntegrationDependencies, jsxConfig, runtimeOrigin, }: EcopagesJsxRendererOptions);
37
+ /** Returns whether the requested page file should be treated as MDX. */
38
+ isMdxFile(filePath: string): boolean;
39
+ protected importPageFile(file: string, options?: RouteModuleLoadOptions): Promise<MdxPageModule>;
41
40
  render(options: IntegrationRendererRenderOptions<JsxRenderable>): Promise<RouteRendererBody>;
42
41
  renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult>;
43
42
  protected createComponentBoundaryRuntime(options: {
@@ -45,10 +44,24 @@ export declare class EcopagesJsxRenderer extends IntegrationRenderer<JsxRenderab
45
44
  rendererCache: Map<string, IntegrationRenderer<any>>;
46
45
  }): import("@ecopages/core").ComponentBoundaryRuntime;
47
46
  renderToResponse<P = any>(view: EcoComponent<P>, props: P, ctx: RenderToResponseContext): Promise<Response>;
47
+ /**
48
+ * Normalizes MDX modules into the same page contract as JSX route modules.
49
+ *
50
+ * MDX files export page metadata alongside generated component code, so the
51
+ * renderer folds those exports back into the Ecopages component shape before
52
+ * any layout or document-shell logic runs.
53
+ */
54
+ private normalizeMdxPageModule;
48
55
  private beginCollectedAssetFrame;
49
56
  private endCollectedAssetFrame;
50
57
  private renderJsx;
51
58
  private renderEcoComponent;
59
+ private ensureRadiantLightDomShimInstalled;
60
+ private isFunctionComponent;
61
+ private createComponentProps;
62
+ private collectComponentAssets;
63
+ private invokeComponent;
64
+ private createEcoMeta;
65
+ private wrapMdxPage;
52
66
  private createIntrinsicCustomElementRenderHook;
53
67
  }
54
- export {};
@@ -1,46 +1,22 @@
1
1
  import { rapidhash } from "@ecopages/core/hash";
2
- import { IntegrationRenderer } from "@ecopages/core/route-renderer/integration-renderer";
2
+ import {
3
+ IntegrationRenderer
4
+ } from "@ecopages/core/route-renderer/integration-renderer";
3
5
  import { createMarkupNodeLike } from "@ecopages/jsx";
4
6
  import { renderToString, withServerCustomElementRenderHook } from "@ecopages/jsx/server";
5
7
  import { ECOPAGES_JSX_PLUGIN_NAME } from "./ecopages-jsx.plugin.js";
6
- let radiantLightDomShimInstallPromise;
7
- async function ensureRadiantLightDomShimInstalled() {
8
- if (!radiantLightDomShimInstallPromise) {
9
- radiantLightDomShimInstallPromise = import("@ecopages/radiant/server/light-dom-shim").then((module) => {
10
- module.installLightDomShim();
11
- }).then(() => void 0);
12
- }
13
- await radiantLightDomShimInstallPromise;
14
- }
15
- const isEcoFunctionComponent = (component) => {
16
- return typeof component === "function";
17
- };
18
- const renderComponent = async (component, props) => {
19
- return await component(props);
20
- };
21
- const createEcoMeta = (file) => ({
22
- id: String(rapidhash(file)),
23
- file,
24
- integration: ECOPAGES_JSX_PLUGIN_NAME
25
- });
26
- const wrapMdxPage = (page, {
27
- config,
28
- metadata
29
- }) => {
30
- const wrappedPage = (async (props) => renderComponent(page, props));
31
- wrappedPage.config = config;
32
- if (metadata) {
33
- wrappedPage.metadata = metadata;
34
- }
35
- return wrappedPage;
36
- };
37
8
  class EcopagesJsxRenderer extends IntegrationRenderer {
38
9
  name = ECOPAGES_JSX_PLUGIN_NAME;
39
- static mdxExtensions = [".mdx"];
40
- intrinsicCustomElementAssets = /* @__PURE__ */ new Map();
10
+ static radiantLightDomShimInstallPromise;
11
+ intrinsicCustomElementAssets;
41
12
  collectedAssetFrames = [];
42
- radiantSsrEnabled = false;
43
- async resolveQueuedBoundaryChildren(children, queuedResolutionsByToken, resolveToken) {
13
+ mdxExtensions;
14
+ radiantSsrEnabled;
15
+ /**
16
+ * Re-renders queued JSX children inside the owning renderer so nested custom
17
+ * elements and queued foreign boundaries contribute assets to the same frame.
18
+ */
19
+ async renderQueuedBoundaryChildren(children, queuedResolutionsByToken, resolveToken) {
44
20
  if (children === void 0) {
45
21
  return { assets: [] };
46
22
  }
@@ -59,18 +35,25 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
59
35
  html
60
36
  };
61
37
  }
62
- async resolveQueuedBoundaryHtml(html, runtimeContext) {
38
+ /**
39
+ * Resolves queued foreign boundaries after JSX has been stringified.
40
+ *
41
+ * JSX content needs one extra render pass because child boundaries may emit
42
+ * additional browser assets while also replacing placeholder tokens.
43
+ */
44
+ async resolveOwnedBoundaryHtml(html, runtimeContext) {
63
45
  return this.resolveRendererOwnedQueuedBoundaryHtml({
64
46
  html,
65
47
  runtimeContext,
66
48
  queueLabel: "Ecopages JSX",
67
- renderQueuedChildren: async (children, _runtimeContext, queuedResolutionsByToken, resolveToken) => this.resolveQueuedBoundaryChildren(children, queuedResolutionsByToken, resolveToken)
49
+ renderQueuedChildren: async (children, _runtimeContext, queuedResolutionsByToken, resolveToken) => this.renderQueuedBoundaryChildren(children, queuedResolutionsByToken, resolveToken)
68
50
  });
69
51
  }
70
52
  constructor({
71
53
  appConfig,
72
54
  assetProcessingService,
73
55
  resolvedIntegrationDependencies,
56
+ jsxConfig,
74
57
  runtimeOrigin
75
58
  }) {
76
59
  super({
@@ -79,42 +62,20 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
79
62
  resolvedIntegrationDependencies,
80
63
  runtimeOrigin
81
64
  });
65
+ this.intrinsicCustomElementAssets = jsxConfig?.intrinsicCustomElementAssets ?? /* @__PURE__ */ new Map();
66
+ this.mdxExtensions = jsxConfig?.mdxExtensions ?? [".mdx"];
67
+ this.radiantSsrEnabled = jsxConfig?.radiantSsrEnabled ?? false;
82
68
  }
83
69
  /** Returns whether the requested page file should be treated as MDX. */
84
70
  isMdxFile(filePath) {
85
- return EcopagesJsxRenderer.mdxExtensions.some((ext) => filePath.endsWith(ext));
86
- }
87
- /**
88
- * Supplies the intrinsic custom-element assets discovered by the plugin so
89
- * component renders can attach the correct client scripts.
90
- */
91
- setIntrinsicCustomElementAssets(assetsByTagName) {
92
- this.intrinsicCustomElementAssets = assetsByTagName;
93
- }
94
- /** Enables the Radiant SSR light-DOM shim for this renderer instance. */
95
- setRadiantSsrEnabled(enabled) {
96
- this.radiantSsrEnabled = enabled;
71
+ return this.mdxExtensions.some((ext) => filePath.endsWith(ext));
97
72
  }
98
- async importPageFile(file) {
99
- const module = await super.importPageFile(file);
100
- if (!this.isMdxFile(file)) {
101
- return module;
73
+ async importPageFile(file, options) {
74
+ if (this.radiantSsrEnabled) {
75
+ await this.ensureRadiantLightDomShimInstalled();
102
76
  }
103
- const Page = module.default;
104
- const normalizedConfig = {
105
- ...module.config ?? Page.config ?? {},
106
- ...module.layout ? { layout: module.layout } : {},
107
- __eco: module.config?.__eco ?? Page.config?.__eco ?? createEcoMeta(file)
108
- };
109
- const wrappedPage = wrapMdxPage(Page, {
110
- config: normalizedConfig,
111
- metadata: module.getMetadata ?? Page.metadata
112
- });
113
- return {
114
- ...module,
115
- default: wrappedPage,
116
- config: normalizedConfig
117
- };
77
+ const module = await super.importPageFile(file, options);
78
+ return this.isMdxFile(file) ? this.normalizeMdxPageModule(file, module) : module;
118
79
  }
119
80
  async render(options) {
120
81
  try {
@@ -144,26 +105,19 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
144
105
  async renderComponent(input) {
145
106
  const assetFrame = this.beginCollectedAssetFrame();
146
107
  try {
147
- if (!isEcoFunctionComponent(input.component)) {
108
+ if (!this.isFunctionComponent(input.component)) {
148
109
  throw new TypeError("JSX renderer expected a callable component.");
149
110
  }
150
- let props = input.props;
151
- if (input.children !== void 0) {
152
- props = {
153
- ...input.props,
154
- children: typeof input.children === "string" ? createMarkupNodeLike(input.children) : input.children
155
- };
156
- }
157
111
  const content = await this.renderEcoComponent(
158
112
  input.component,
159
- props
113
+ this.createComponentProps(input)
160
114
  );
161
115
  const rendered = await this.renderJsx(content);
162
- const queuedBoundaryResolution = await this.resolveQueuedBoundaryHtml(
116
+ const queuedBoundaryResolution = await this.resolveOwnedBoundaryHtml(
163
117
  rendered.html,
164
118
  this.getQueuedBoundaryRuntime(input)
165
119
  );
166
- const componentAssets = input.component.config?.dependencies && typeof this.assetProcessingService?.processDependencies === "function" ? await this.processComponentDependencies([input.component]) : [];
120
+ const componentAssets = await this.collectComponentAssets(input.component);
167
121
  const assets = this.htmlTransformer.dedupeProcessedAssets([
168
122
  ...this.endCollectedAssetFrame(assetFrame),
169
123
  ...queuedBoundaryResolution.assets,
@@ -189,7 +143,7 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
189
143
  }
190
144
  async renderToResponse(view, props, ctx) {
191
145
  try {
192
- if (!isEcoFunctionComponent(view)) {
146
+ if (!this.isFunctionComponent(view)) {
193
147
  throw new TypeError("JSX renderer expected a callable view component.");
194
148
  }
195
149
  return await this.renderViewWithDocumentShell({
@@ -202,6 +156,30 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
202
156
  throw this.createRenderError("Error rendering view", error);
203
157
  }
204
158
  }
159
+ /**
160
+ * Normalizes MDX modules into the same page contract as JSX route modules.
161
+ *
162
+ * MDX files export page metadata alongside generated component code, so the
163
+ * renderer folds those exports back into the Ecopages component shape before
164
+ * any layout or document-shell logic runs.
165
+ */
166
+ normalizeMdxPageModule(file, module) {
167
+ const Page = module.default;
168
+ const normalizedConfig = {
169
+ ...module.config ?? Page.config ?? {},
170
+ ...module.layout ? { layout: module.layout } : {},
171
+ __eco: module.config?.__eco ?? Page.config?.__eco ?? this.createEcoMeta(file)
172
+ };
173
+ const wrappedPage = this.wrapMdxPage(Page, {
174
+ config: normalizedConfig,
175
+ metadata: module.getMetadata ?? Page.metadata
176
+ });
177
+ return {
178
+ ...module,
179
+ default: wrappedPage,
180
+ config: normalizedConfig
181
+ };
182
+ }
205
183
  beginCollectedAssetFrame() {
206
184
  const frame = [];
207
185
  this.collectedAssetFrames.push(frame);
@@ -216,7 +194,7 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
216
194
  }
217
195
  async renderJsx(value) {
218
196
  if (this.radiantSsrEnabled) {
219
- await ensureRadiantLightDomShimInstalled();
197
+ await this.ensureRadiantLightDomShimInstalled();
220
198
  }
221
199
  const collectedAssets = [];
222
200
  const html = withServerCustomElementRenderHook(
@@ -235,12 +213,12 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
235
213
  }
236
214
  async renderEcoComponent(component, props) {
237
215
  if (this.radiantSsrEnabled) {
238
- await ensureRadiantLightDomShimInstalled();
216
+ await this.ensureRadiantLightDomShimInstalled();
239
217
  }
240
218
  const collectedAssets = [];
241
219
  const rendered = await withServerCustomElementRenderHook(
242
220
  this.createIntrinsicCustomElementRenderHook(collectedAssets),
243
- () => renderComponent(component, props)
221
+ () => this.invokeComponent(component, props)
244
222
  );
245
223
  const activeFrame = this.collectedAssetFrames[this.collectedAssetFrames.length - 1];
246
224
  if (activeFrame) {
@@ -248,6 +226,53 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
248
226
  }
249
227
  return rendered;
250
228
  }
229
+ async ensureRadiantLightDomShimInstalled() {
230
+ if (!EcopagesJsxRenderer.radiantLightDomShimInstallPromise) {
231
+ EcopagesJsxRenderer.radiantLightDomShimInstallPromise = import("@ecopages/radiant/server/light-dom-shim").then((module) => {
232
+ module.installLightDomShim();
233
+ }).then(() => void 0);
234
+ }
235
+ await EcopagesJsxRenderer.radiantLightDomShimInstallPromise;
236
+ }
237
+ isFunctionComponent(component) {
238
+ return typeof component === "function";
239
+ }
240
+ createComponentProps(input) {
241
+ if (input.children === void 0) {
242
+ return input.props;
243
+ }
244
+ return {
245
+ ...input.props,
246
+ children: typeof input.children === "string" ? createMarkupNodeLike(input.children) : input.children
247
+ };
248
+ }
249
+ async collectComponentAssets(component) {
250
+ if (!component.config?.dependencies || typeof this.assetProcessingService?.processDependencies !== "function") {
251
+ return [];
252
+ }
253
+ return this.processComponentDependencies([component]);
254
+ }
255
+ async invokeComponent(component, props) {
256
+ return await component(props);
257
+ }
258
+ createEcoMeta(file) {
259
+ return {
260
+ id: String(rapidhash(file)),
261
+ file,
262
+ integration: ECOPAGES_JSX_PLUGIN_NAME
263
+ };
264
+ }
265
+ wrapMdxPage(page, {
266
+ config,
267
+ metadata
268
+ }) {
269
+ const wrappedPage = (async (props) => this.invokeComponent(page, props));
270
+ wrappedPage.config = config;
271
+ if (metadata) {
272
+ wrappedPage.metadata = metadata;
273
+ }
274
+ return wrappedPage;
275
+ }
251
276
  createIntrinsicCustomElementRenderHook(target) {
252
277
  return ({ tagName }) => {
253
278
  const assets = this.intrinsicCustomElementAssets.get(tagName);
@@ -1,14 +1,9 @@
1
- import type { CompileOptions } from '@mdx-js/mdx';
2
1
  import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
3
- import { IntegrationPlugin, type IntegrationPluginConfig } from '@ecopages/core/plugins/integration-plugin';
2
+ import { IntegrationPlugin } from '@ecopages/core/plugins/integration-plugin';
4
3
  import type { JsxRenderable } from '@ecopages/jsx';
5
4
  import { EcopagesJsxRenderer } from './ecopages-jsx-renderer.js';
6
- type MdxPluginList = NonNullable<CompileOptions['remarkPlugins']>;
7
- type MdxCompileOptions = Omit<CompileOptions, 'jsxImportSource' | 'jsxRuntime' | 'remarkPlugins' | 'rehypePlugins' | 'recmaPlugins'> & {
8
- remarkPlugins?: MdxPluginList;
9
- rehypePlugins?: MdxPluginList;
10
- recmaPlugins?: MdxPluginList;
11
- };
5
+ import type { EcopagesJsxPluginOptions } from './ecopages-jsx.types.js';
6
+ export type { EcopagesJsxMdxCompileOptions, EcopagesJsxMdxOptions, EcopagesJsxPluginOptions, EcopagesJsxRendererConfig, } from './ecopages-jsx.types.js';
12
7
  /**
13
8
  * Stable integration name shared by the JSX plugin and renderer.
14
9
  *
@@ -16,46 +11,10 @@ type MdxCompileOptions = Omit<CompileOptions, 'jsxImportSource' | 'jsxRuntime' |
16
11
  * cross-integration component boundaries.
17
12
  */
18
13
  export declare const ECOPAGES_JSX_PLUGIN_NAME = "ecopages-jsx";
19
- /**
20
- * MDX configuration for the JSX integration.
21
- *
22
- * Mirrors Ecopages' built-in combined integration pattern where a single
23
- * JSX-capable plugin can own both `.tsx` and `.mdx` route files.
24
- */
25
- export type EcopagesJsxMdxOptions = {
26
- /** Enables MDX file handling inside the JSX integration. */
27
- enabled: boolean;
28
- /** Additional MDX compiler options. JSX runtime fields are managed by the integration. */
29
- compilerOptions?: MdxCompileOptions;
30
- /** Extra remark plugins appended to `compilerOptions.remarkPlugins`. */
31
- remarkPlugins?: MdxPluginList;
32
- /** Extra rehype plugins appended to `compilerOptions.rehypePlugins`. */
33
- rehypePlugins?: MdxPluginList;
34
- /** Extra recma plugins appended to `compilerOptions.recmaPlugins`. */
35
- recmaPlugins?: MdxPluginList;
36
- /** Custom file extensions to treat as MDX. */
37
- extensions?: string[];
38
- };
39
- /** Options for the JSX integration plugin. */
40
- export type EcopagesJsxPluginOptions = Omit<IntegrationPluginConfig, 'name' | 'extensions'> & {
41
- /** Optional JSX route extensions. Defaults to `.tsx`. */
42
- extensions?: string[];
43
- /**
44
- * Whether to include the `@ecopages/radiant` and `@ecopages/signals` vendor
45
- * bundles and bare-specifier mappings.
46
- *
47
- * Set to `false` when pages do not use Radiant web components.
48
- * @default true
49
- */
50
- radiant?: boolean;
51
- /** Optional MDX integration configuration. */
52
- mdx?: EcopagesJsxMdxOptions;
53
- };
54
14
  /** JSX integration plugin for Ecopages, supporting `.tsx` templates and optional Radiant web components. */
55
15
  export declare class EcopagesJsxPlugin extends IntegrationPlugin<JsxRenderable> {
56
- /** Renderer implementation used for JSX and MDX routes. */
57
- renderer: IntegrationPlugin<JsxRenderable>["renderer"];
58
- private intrinsicCustomElementAssets;
16
+ renderer: typeof EcopagesJsxRenderer;
17
+ private customElementAssets;
59
18
  private includeRadiant;
60
19
  private mdxEnabled;
61
20
  private mdxCompilerOptions?;
@@ -79,13 +38,9 @@ export declare class EcopagesJsxPlugin extends IntegrationPlugin<JsxRenderable>
79
38
  * Creates the renderer instance and attaches the discovered intrinsic custom
80
39
  * element assets before the renderer handles any requests.
81
40
  */
82
- initializeRenderer(): EcopagesJsxRenderer;
83
- /**
84
- * Creates a JSX integration plugin.
85
- *
86
- * When MDX is enabled, the plugin also claims the configured MDX extensions
87
- * and prepares compiler options around the shared `@ecopages/jsx` runtime.
88
- */
41
+ initializeRenderer(options?: {
42
+ rendererModules?: unknown;
43
+ }): EcopagesJsxRenderer;
89
44
  constructor(options?: EcopagesJsxPluginOptions);
90
45
  /** Ensures MDX build hooks are ready before Ecopages collects contributions. */
91
46
  prepareBuildContributions(): Promise<void>;
@@ -95,14 +50,25 @@ export declare class EcopagesJsxPlugin extends IntegrationPlugin<JsxRenderable>
95
50
  */
96
51
  setup(): Promise<void>;
97
52
  private ensureMdxLoaderPlugin;
98
- private setupMdxBunPlugin;
99
- private buildIntrinsicCustomElementAssetRegistry;
100
- private collectScriptEntryFiles;
101
- private resolveIntrinsicCustomElementAsset;
102
- private extractIntrinsicCustomElementTagNames;
53
+ /**
54
+ * Registers Bun's MDX loader at runtime setup time.
55
+ *
56
+ * Build-time contribution collection may run where Bun is absent, so
57
+ * this hook stays isolated from manifest preparation.
58
+ */
59
+ private registerMdxBunPlugin;
60
+ /**
61
+ * Scans `src/` for custom-element entry scripts and pre-resolves their assets.
62
+ *
63
+ * The renderer's server-side custom-element hook relies on this registry to
64
+ * attach browser scripts without per-render file-system lookups.
65
+ */
66
+ private buildCustomElementRegistry;
67
+ private collectScriptEntries;
68
+ private resolveCustomElementAsset;
69
+ private extractCustomElementTagNames;
103
70
  }
104
71
  /**
105
- * Creates the JSX integration plugin.
72
+ * Creates the JSX integration plugin with resolved defaults.
106
73
  */
107
74
  export declare const ecopagesJsxPlugin: (options?: EcopagesJsxPluginOptions) => EcopagesJsxPlugin;
108
- export {};
@@ -5,14 +5,44 @@ import { AssetFactory } from "@ecopages/core/services/asset-processing-service";
5
5
  import { VFile } from "vfile";
6
6
  import { EcopagesJsxRenderer } from "./ecopages-jsx-renderer.js";
7
7
  import { JsxRuntimeBundleService } from "./services/jsx-runtime-bundle.service.js";
8
+ const ECOPAGES_JSX_PLUGIN_NAME = "ecopages-jsx";
9
+ const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8
10
  const mergePluginLists = (...lists) => {
9
11
  const merged = lists.flatMap((list) => list ? [...list] : []);
10
12
  return merged.length > 0 ? merged : void 0;
11
13
  };
12
- const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14
+ const createMdxExtensionFilter = (extensions, options) => {
15
+ const escaped = extensions.map(escapeRegex);
16
+ const suffix = options?.allowQueryString ? "(\\?.*)?$" : "$";
17
+ return new RegExp(`(${escaped.join("|")})${suffix}`);
18
+ };
19
+ const appendMdxExtensions = (target, mdxExtensions) => {
20
+ for (const ext of mdxExtensions) {
21
+ if (!target.includes(ext)) {
22
+ target.push(ext);
23
+ }
24
+ }
25
+ };
26
+ const resolveMdxCompilerOptions = (mdxOptions) => {
27
+ const { compilerOptions, remarkPlugins, rehypePlugins, recmaPlugins } = mdxOptions;
28
+ const resolved = {
29
+ format: "detect",
30
+ outputFormat: "program",
31
+ ...compilerOptions,
32
+ jsxImportSource: "@ecopages/jsx",
33
+ jsxRuntime: "automatic",
34
+ development: process.env.NODE_ENV === "development"
35
+ };
36
+ const mergedRemark = mergePluginLists(compilerOptions?.remarkPlugins, remarkPlugins);
37
+ const mergedRehype = mergePluginLists(compilerOptions?.rehypePlugins, rehypePlugins);
38
+ const mergedRecma = mergePluginLists(compilerOptions?.recmaPlugins, recmaPlugins);
39
+ if (mergedRemark) resolved.remarkPlugins = mergedRemark;
40
+ if (mergedRehype) resolved.rehypePlugins = mergedRehype;
41
+ if (mergedRecma) resolved.recmaPlugins = mergedRecma;
42
+ return resolved;
43
+ };
13
44
  const createMdxLoaderPlugin = (compilerOptions, extensions) => {
14
- const escapedExts = extensions.map(escapeRegex);
15
- const filter = new RegExp(`(${escapedExts.join("|")})(\\?.*)?$`);
45
+ const filter = createMdxExtensionFilter(extensions, { allowQueryString: true });
16
46
  return {
17
47
  name: "ecopages-jsx-mdx-loader",
18
48
  setup(build) {
@@ -30,11 +60,26 @@ const createMdxLoaderPlugin = (compilerOptions, extensions) => {
30
60
  }
31
61
  };
32
62
  };
33
- const ECOPAGES_JSX_PLUGIN_NAME = "ecopages-jsx";
63
+ const resolvePluginOptions = (options) => {
64
+ const { extensions: userExtensions, radiant, mdx, ...baseConfig } = options ?? {};
65
+ const extensions = [...userExtensions ?? [".tsx"]];
66
+ const mdxExtensions = mdx?.extensions ?? [".mdx"];
67
+ const mdxEnabled = mdx?.enabled ?? false;
68
+ if (mdxEnabled) {
69
+ appendMdxExtensions(extensions, mdxExtensions);
70
+ }
71
+ return {
72
+ ...baseConfig,
73
+ extensions,
74
+ includeRadiant: radiant ?? true,
75
+ mdxEnabled,
76
+ mdxExtensions,
77
+ mdxCompilerOptions: mdxEnabled && mdx ? resolveMdxCompilerOptions(mdx) : void 0
78
+ };
79
+ };
34
80
  class EcopagesJsxPlugin extends IntegrationPlugin {
35
- /** Renderer implementation used for JSX and MDX routes. */
36
81
  renderer = EcopagesJsxRenderer;
37
- intrinsicCustomElementAssets = /* @__PURE__ */ new Map();
82
+ customElementAssets = /* @__PURE__ */ new Map();
38
83
  includeRadiant;
39
84
  mdxEnabled;
40
85
  mdxCompilerOptions;
@@ -66,65 +111,32 @@ class EcopagesJsxPlugin extends IntegrationPlugin {
66
111
  * Creates the renderer instance and attaches the discovered intrinsic custom
67
112
  * element assets before the renderer handles any requests.
68
113
  */
69
- initializeRenderer() {
70
- const renderer = super.initializeRenderer();
71
- renderer.setIntrinsicCustomElementAssets(this.intrinsicCustomElementAssets);
72
- renderer.setRadiantSsrEnabled(this.includeRadiant);
73
- return renderer;
114
+ initializeRenderer(options) {
115
+ const rendererConfig = {
116
+ intrinsicCustomElementAssets: this.customElementAssets,
117
+ mdxExtensions: this.mdxExtensions,
118
+ radiantSsrEnabled: this.includeRadiant
119
+ };
120
+ const renderer = new this.renderer({
121
+ ...this.createRendererOptions(options),
122
+ jsxConfig: rendererConfig
123
+ });
124
+ return this.attachRendererRuntimeServices(renderer);
74
125
  }
75
- /**
76
- * Creates a JSX integration plugin.
77
- *
78
- * When MDX is enabled, the plugin also claims the configured MDX extensions
79
- * and prepares compiler options around the shared `@ecopages/jsx` runtime.
80
- */
81
126
  constructor(options) {
82
- const { extensions: _ignoredExtensions, ...restOptions } = options ?? {};
83
- const extensions = [...options?.extensions ?? [".tsx"]];
84
- const mdxExtensions = options?.mdx?.extensions ?? [".mdx"];
85
- const includeRadiant = options?.radiant ?? true;
86
- if (options?.mdx?.enabled) {
87
- for (const extension of mdxExtensions) {
88
- if (!extensions.includes(extension)) {
89
- extensions.push(extension);
90
- }
91
- }
92
- }
127
+ const config = resolvePluginOptions(options);
128
+ const { extensions, includeRadiant, mdxEnabled, mdxExtensions, mdxCompilerOptions, ...baseConfig } = config;
93
129
  super({
94
130
  name: ECOPAGES_JSX_PLUGIN_NAME,
95
131
  extensions,
96
132
  jsxImportSource: "@ecopages/jsx",
97
- ...restOptions
133
+ ...baseConfig
98
134
  });
99
135
  this.includeRadiant = includeRadiant;
100
136
  this.runtimeBundleService = new JsxRuntimeBundleService({ radiant: includeRadiant });
101
- this.mdxEnabled = options?.mdx?.enabled ?? false;
137
+ this.mdxEnabled = mdxEnabled;
102
138
  this.mdxExtensions = mdxExtensions;
103
- EcopagesJsxRenderer.mdxExtensions = this.mdxExtensions;
104
- if (this.mdxEnabled) {
105
- const { compilerOptions, remarkPlugins, rehypePlugins, recmaPlugins } = options?.mdx ?? {};
106
- const resolvedCompilerOptions = {
107
- format: "detect",
108
- outputFormat: "program",
109
- ...compilerOptions,
110
- jsxImportSource: "@ecopages/jsx",
111
- jsxRuntime: "automatic",
112
- development: process.env.NODE_ENV === "development"
113
- };
114
- const mergedRemarkPlugins = mergePluginLists(compilerOptions?.remarkPlugins, remarkPlugins);
115
- const mergedRehypePlugins = mergePluginLists(compilerOptions?.rehypePlugins, rehypePlugins);
116
- const mergedRecmaPlugins = mergePluginLists(compilerOptions?.recmaPlugins, recmaPlugins);
117
- if (mergedRemarkPlugins) {
118
- resolvedCompilerOptions.remarkPlugins = mergedRemarkPlugins;
119
- }
120
- if (mergedRehypePlugins) {
121
- resolvedCompilerOptions.rehypePlugins = mergedRehypePlugins;
122
- }
123
- if (mergedRecmaPlugins) {
124
- resolvedCompilerOptions.recmaPlugins = mergedRecmaPlugins;
125
- }
126
- this.mdxCompilerOptions = resolvedCompilerOptions;
127
- }
139
+ this.mdxCompilerOptions = mdxCompilerOptions;
128
140
  }
129
141
  /** Ensures MDX build hooks are ready before Ecopages collects contributions. */
130
142
  async prepareBuildContributions() {
@@ -144,9 +156,9 @@ class EcopagesJsxPlugin extends IntegrationPlugin {
144
156
  async setup() {
145
157
  this.ensureMdxLoaderPlugin();
146
158
  if (typeof Bun !== "undefined" && this.mdxEnabled && this.mdxCompilerOptions) {
147
- await this.setupMdxBunPlugin();
159
+ await this.registerMdxBunPlugin();
148
160
  }
149
- await this.buildIntrinsicCustomElementAssetRegistry();
161
+ await this.buildCustomElementRegistry();
150
162
  await super.setup();
151
163
  }
152
164
  ensureMdxLoaderPlugin() {
@@ -155,13 +167,18 @@ class EcopagesJsxPlugin extends IntegrationPlugin {
155
167
  }
156
168
  this.mdxLoaderPlugin = createMdxLoaderPlugin(this.mdxCompilerOptions, this.mdxExtensions);
157
169
  }
158
- async setupMdxBunPlugin() {
159
- if (typeof Bun === "undefined" || !this.mdxEnabled || !this.mdxCompilerOptions) {
170
+ /**
171
+ * Registers Bun's MDX loader at runtime setup time.
172
+ *
173
+ * Build-time contribution collection may run where Bun is absent, so
174
+ * this hook stays isolated from manifest preparation.
175
+ */
176
+ async registerMdxBunPlugin() {
177
+ if (typeof Bun === "undefined" || !this.mdxCompilerOptions) {
160
178
  return;
161
179
  }
162
180
  const compilerOptions = this.mdxCompilerOptions;
163
- const escapedExts = this.mdxExtensions.map(escapeRegex);
164
- const filter = new RegExp(`(${escapedExts.join("|")})$`);
181
+ const filter = createMdxExtensionFilter(this.mdxExtensions);
165
182
  Bun.plugin({
166
183
  name: "ecopages-jsx-mdx",
167
184
  setup(build) {
@@ -174,57 +191,67 @@ class EcopagesJsxPlugin extends IntegrationPlugin {
174
191
  }
175
192
  });
176
193
  }
177
- async buildIntrinsicCustomElementAssetRegistry() {
194
+ /**
195
+ * Scans `src/` for custom-element entry scripts and pre-resolves their assets.
196
+ *
197
+ * The renderer's server-side custom-element hook relies on this registry to
198
+ * attach browser scripts without per-render file-system lookups.
199
+ */
200
+ async buildCustomElementRegistry() {
178
201
  if (!this.appConfig || !this.assetProcessingService) {
179
202
  return;
180
203
  }
181
- this.intrinsicCustomElementAssets.clear();
182
- const scriptFiles = await this.collectScriptEntryFiles(this.appConfig.absolutePaths.srcDir);
204
+ this.customElementAssets.clear();
205
+ const scriptFiles = await this.collectScriptEntries(this.appConfig.absolutePaths.srcDir);
183
206
  for (const scriptFile of scriptFiles) {
184
- const tagNames = await this.extractIntrinsicCustomElementTagNames(scriptFile);
207
+ const tagNames = await this.extractCustomElementTagNames(scriptFile);
185
208
  if (tagNames.length === 0) {
186
209
  continue;
187
210
  }
188
- const processedAsset = await this.resolveIntrinsicCustomElementAsset(scriptFile);
189
- if (!processedAsset) {
211
+ const asset = await this.resolveCustomElementAsset(scriptFile);
212
+ if (!asset) {
190
213
  continue;
191
214
  }
192
215
  for (const tagName of tagNames) {
193
- this.intrinsicCustomElementAssets.set(tagName, [processedAsset]);
216
+ this.customElementAssets.set(tagName, [asset]);
194
217
  }
195
218
  }
196
219
  }
197
- async collectScriptEntryFiles(directory) {
198
- const directoryEntries = await readdir(directory, { withFileTypes: true });
199
- const scriptFiles = [];
200
- for (const directoryEntry of directoryEntries) {
201
- const entryPath = path.join(directory, directoryEntry.name);
202
- if (directoryEntry.isDirectory()) {
203
- scriptFiles.push(...await this.collectScriptEntryFiles(entryPath));
220
+ async collectScriptEntries(directory) {
221
+ const entries = await readdir(directory, { withFileTypes: true });
222
+ const scripts = [];
223
+ for (const entry of entries) {
224
+ const entryPath = path.join(directory, entry.name);
225
+ if (entry.isDirectory()) {
226
+ scripts.push(...await this.collectScriptEntries(entryPath));
204
227
  continue;
205
228
  }
206
- if (/\.script\.(?:ts|tsx)$/.test(directoryEntry.name)) {
207
- scriptFiles.push(entryPath);
229
+ if (/\.script\.(?:ts|tsx)$/.test(entry.name)) {
230
+ scripts.push(entryPath);
208
231
  }
209
232
  }
210
- return scriptFiles;
233
+ return scripts;
211
234
  }
212
- async resolveIntrinsicCustomElementAsset(scriptFile) {
235
+ async resolveCustomElementAsset(scriptFile) {
213
236
  if (!this.assetProcessingService) {
214
237
  return void 0;
215
238
  }
216
- const [processedAsset] = await this.assetProcessingService.processDependencies(
239
+ const [asset] = await this.assetProcessingService.processDependencies(
217
240
  [
218
241
  AssetFactory.createFileScript({
219
242
  filepath: scriptFile,
220
- position: "head"
243
+ position: "head",
244
+ attributes: {
245
+ type: "module",
246
+ defer: ""
247
+ }
221
248
  })
222
249
  ],
223
- `${this.name}:intrinsic-custom-elements:${scriptFile}`
250
+ `${this.name}:custom-elements:${scriptFile}`
224
251
  );
225
- return processedAsset;
252
+ return asset;
226
253
  }
227
- async extractIntrinsicCustomElementTagNames(scriptFile) {
254
+ async extractCustomElementTagNames(scriptFile) {
228
255
  const source = await readFile(scriptFile, "utf8");
229
256
  const tagNames = /* @__PURE__ */ new Set();
230
257
  for (const match of source.matchAll(/@customElement\(\s*['"]([^'"]+)['"]/g)) {
@@ -233,6 +260,12 @@ class EcopagesJsxPlugin extends IntegrationPlugin {
233
260
  tagNames.add(tagName);
234
261
  }
235
262
  }
263
+ for (const match of source.matchAll(/customElement\(\s*['"]([^'"]+)['"]\s*\)\s*\(/g)) {
264
+ const tagName = match[1];
265
+ if (tagName) {
266
+ tagNames.add(tagName);
267
+ }
268
+ }
236
269
  return [...tagNames];
237
270
  }
238
271
  }
@@ -0,0 +1,59 @@
1
+ import type { CompileOptions } from '@mdx-js/mdx';
2
+ import type { IntegrationPluginConfig } from '@ecopages/core/plugins/integration-plugin';
3
+ import type { AssetDefinition, AssetProcessingService, ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
4
+ import type { EcoPagesAppConfig } from '@ecopages/core/internal-types';
5
+ type MdxPluginList = NonNullable<CompileOptions['remarkPlugins']>;
6
+ export type EcopagesJsxMdxCompileOptions = Omit<CompileOptions, 'jsxImportSource' | 'jsxRuntime' | 'remarkPlugins' | 'rehypePlugins' | 'recmaPlugins'> & {
7
+ remarkPlugins?: MdxPluginList;
8
+ rehypePlugins?: MdxPluginList;
9
+ recmaPlugins?: MdxPluginList;
10
+ };
11
+ /**
12
+ * MDX configuration for the JSX integration.
13
+ *
14
+ * Mirrors Ecopages' built-in combined integration pattern where a single
15
+ * JSX-capable plugin can own both `.tsx` and `.mdx` route files.
16
+ */
17
+ export type EcopagesJsxMdxOptions = {
18
+ /** Enables MDX file handling inside the JSX integration. */
19
+ enabled: boolean;
20
+ /** Additional MDX compiler options. JSX runtime fields are managed by the integration. */
21
+ compilerOptions?: EcopagesJsxMdxCompileOptions;
22
+ /** Extra remark plugins appended to `compilerOptions.remarkPlugins`. */
23
+ remarkPlugins?: MdxPluginList;
24
+ /** Extra rehype plugins appended to `compilerOptions.rehypePlugins`. */
25
+ rehypePlugins?: MdxPluginList;
26
+ /** Extra recma plugins appended to `compilerOptions.recmaPlugins`. */
27
+ recmaPlugins?: MdxPluginList;
28
+ /** Custom file extensions to treat as MDX. */
29
+ extensions?: string[];
30
+ };
31
+ /** Options for the JSX integration plugin. */
32
+ export type EcopagesJsxPluginOptions = Omit<IntegrationPluginConfig, 'name' | 'extensions'> & {
33
+ /** Optional JSX route extensions. Defaults to `.tsx`. */
34
+ extensions?: string[];
35
+ /**
36
+ * Whether to include the `@ecopages/radiant` and `@ecopages/signals` vendor
37
+ * bundles and bare-specifier mappings.
38
+ *
39
+ * Set to `false` when pages do not use Radiant web components.
40
+ * @default true
41
+ */
42
+ radiant?: boolean;
43
+ /** Optional MDX integration configuration. */
44
+ mdx?: EcopagesJsxMdxOptions;
45
+ };
46
+ export type EcopagesJsxRendererConfig = {
47
+ intrinsicCustomElementAssets?: Map<string, readonly ProcessedAsset[]>;
48
+ mdxExtensions?: string[];
49
+ radiantSsrEnabled?: boolean;
50
+ };
51
+ export type EcopagesJsxRendererOptions = {
52
+ appConfig: EcoPagesAppConfig;
53
+ assetProcessingService: AssetProcessingService;
54
+ resolvedIntegrationDependencies: ProcessedAsset[];
55
+ rendererModules?: unknown;
56
+ runtimeOrigin: string;
57
+ jsxConfig?: EcopagesJsxRendererConfig;
58
+ };
59
+ export type { AssetDefinition };
File without changes
@@ -17,6 +17,7 @@ export interface JsxRuntimeBundleServiceConfig {
17
17
  export declare class JsxRuntimeBundleService {
18
18
  private readonly config;
19
19
  private cachedSpecifierMap;
20
+ private cachedJsxEntryModulePath;
20
21
  private cachedRadiantEntryModulePath;
21
22
  constructor(config: JsxRuntimeBundleServiceConfig);
22
23
  setRootDir(rootDir: string | undefined): void;
@@ -44,8 +45,9 @@ export declare class JsxRuntimeBundleService {
44
45
  */
45
46
  getDependencies(): Promise<AssetDefinition[]>;
46
47
  private getOrCreateSpecifierMap;
48
+ private getOrCreateJsxEntryModulePath;
47
49
  private getRadiantBrowserRuntimeSpecifiers;
48
50
  private getRadiantBrowserRuntimeModules;
49
- private resolveRadiantExportModulePath;
51
+ private resolvePackageExportModulePath;
50
52
  private getOrCreateRadiantEntryModulePath;
51
53
  }
@@ -60,6 +60,7 @@ function findPackageManifestPath(packageName) {
60
60
  class JsxRuntimeBundleService {
61
61
  config;
62
62
  cachedSpecifierMap;
63
+ cachedJsxEntryModulePath;
63
64
  cachedRadiantEntryModulePath;
64
65
  constructor(config) {
65
66
  this.config = config;
@@ -97,6 +98,7 @@ class JsxRuntimeBundleService {
97
98
  */
98
99
  async getDependencies() {
99
100
  const specifierMap = await this.getSpecifierMap();
101
+ const jsxEntryModulePath = await this.getOrCreateJsxEntryModulePath();
100
102
  const deps = [
101
103
  AssetFactory.createInlineContentScript({
102
104
  position: "head",
@@ -105,7 +107,7 @@ class JsxRuntimeBundleService {
105
107
  attributes: { type: "importmap" }
106
108
  }),
107
109
  createBrowserRuntimeScriptAsset({
108
- importPath: "@ecopages/jsx",
110
+ importPath: jsxEntryModulePath,
109
111
  name: "ecopages-jsx-esm",
110
112
  fileName: VENDOR_FILE_NAMES.jsx
111
113
  })
@@ -129,7 +131,6 @@ class JsxRuntimeBundleService {
129
131
  const jsxVendorUrl = buildBrowserRuntimeAssetUrl(VENDOR_FILE_NAMES.jsx);
130
132
  const specifierMap = {
131
133
  "@ecopages/jsx": jsxVendorUrl,
132
- "@ecopages/jsx/server": jsxVendorUrl,
133
134
  "@ecopages/jsx/client": jsxVendorUrl,
134
135
  "@ecopages/jsx/jsx-runtime": jsxVendorUrl,
135
136
  "@ecopages/jsx/jsx-dev-runtime": jsxVendorUrl
@@ -150,6 +151,28 @@ class JsxRuntimeBundleService {
150
151
  this.cachedSpecifierMap = specifierMap;
151
152
  return specifierMap;
152
153
  }
154
+ async getOrCreateJsxEntryModulePath() {
155
+ if (this.cachedJsxEntryModulePath) {
156
+ return this.cachedJsxEntryModulePath;
157
+ }
158
+ const rootDir = this.config.rootDir ?? process.cwd();
159
+ const artifactsDir = path.join(rootDir, "node_modules", ".cache", "ecopages-browser-runtime");
160
+ const filePath = path.join(artifactsDir, "ecopages-jsx-esm-entry.mjs");
161
+ const manifestPath = findPackageManifestPath("@ecopages/jsx");
162
+ const packageDir = path.dirname(realpathSync(manifestPath));
163
+ const jsxPkg = JSON.parse(readFileSync(manifestPath, "utf8"));
164
+ const jsxModulePath = this.resolvePackageExportModulePath(
165
+ packageDir,
166
+ ".",
167
+ jsxPkg.exports?.["."]
168
+ );
169
+ const relativeModulePath = path.relative(artifactsDir, jsxModulePath).split(path.sep).join("/");
170
+ const entryImportPath = relativeModulePath.startsWith(".") ? relativeModulePath : `./${relativeModulePath}`;
171
+ mkdirSync(artifactsDir, { recursive: true });
172
+ writeFileSync(filePath, [`export * from '${entryImportPath}';`].join("\n"), "utf8");
173
+ this.cachedJsxEntryModulePath = filePath;
174
+ return filePath;
175
+ }
153
176
  getRadiantBrowserRuntimeSpecifiers() {
154
177
  return this.getRadiantBrowserRuntimeModules().map(({ exportKey }) => `@ecopages/radiant${exportKey.slice(1)}`);
155
178
  }
@@ -159,10 +182,14 @@ class JsxRuntimeBundleService {
159
182
  const radiantPkg = JSON.parse(readFileSync(manifestPath, "utf8"));
160
183
  return Object.entries(radiantPkg.exports ?? {}).filter(([key]) => isBrowserRuntimeRadiantSpecifier(key) && key !== ".").sort(([left], [right]) => left.localeCompare(right)).map(([exportKey, exportTarget]) => ({
161
184
  exportKey,
162
- modulePath: this.resolveRadiantExportModulePath(packageDir, exportKey, exportTarget)
185
+ modulePath: this.resolvePackageExportModulePath(
186
+ packageDir,
187
+ exportKey,
188
+ exportTarget
189
+ )
163
190
  })).filter((module) => existsSync(module.modulePath));
164
191
  }
165
- resolveRadiantExportModulePath(packageDir, exportKey, exportTarget) {
192
+ resolvePackageExportModulePath(packageDir, exportKey, exportTarget) {
166
193
  if (typeof exportTarget === "string") {
167
194
  return path.resolve(packageDir, exportTarget);
168
195
  }