@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 +7 -3
- package/package.json +2 -2
- package/src/ecopages-jsx-renderer.d.ts +36 -23
- package/src/ecopages-jsx-renderer.js +109 -84
- package/src/ecopages-jsx.plugin.d.ts +26 -60
- package/src/ecopages-jsx.plugin.js +117 -84
- package/src/ecopages-jsx.types.d.ts +59 -0
- package/src/ecopages-jsx.types.js +0 -0
- package/src/services/jsx-runtime-bundle.service.d.ts +3 -1
- package/src/services/jsx-runtime-bundle.service.js +31 -4
package/CHANGELOG.md
CHANGED
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
### Bug Fixes
|
|
10
10
|
|
|
11
|
-
- Fixed
|
|
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
|
-
###
|
|
17
|
+
### Refactoring
|
|
14
18
|
|
|
15
|
-
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
20
|
-
private intrinsicCustomElementAssets;
|
|
19
|
+
private static radiantLightDomShimInstallPromise;
|
|
20
|
+
private readonly intrinsicCustomElementAssets;
|
|
21
21
|
private collectedAssetFrames;
|
|
22
|
-
private
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
*
|
|
35
|
-
*
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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 {
|
|
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
|
|
40
|
-
intrinsicCustomElementAssets
|
|
10
|
+
static radiantLightDomShimInstallPromise;
|
|
11
|
+
intrinsicCustomElementAssets;
|
|
41
12
|
collectedAssetFrames = [];
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
return module;
|
|
73
|
+
async importPageFile(file, options) {
|
|
74
|
+
if (this.radiantSsrEnabled) {
|
|
75
|
+
await this.ensureRadiantLightDomShimInstalled();
|
|
102
76
|
}
|
|
103
|
-
const
|
|
104
|
-
|
|
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 (!
|
|
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
|
-
|
|
113
|
+
this.createComponentProps(input)
|
|
160
114
|
);
|
|
161
115
|
const rendered = await this.renderJsx(content);
|
|
162
|
-
const queuedBoundaryResolution = await this.
|
|
116
|
+
const queuedBoundaryResolution = await this.resolveOwnedBoundaryHtml(
|
|
163
117
|
rendered.html,
|
|
164
118
|
this.getQueuedBoundaryRuntime(input)
|
|
165
119
|
);
|
|
166
|
-
const componentAssets =
|
|
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 (!
|
|
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
|
-
() =>
|
|
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
|
|
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
|
|
7
|
-
type
|
|
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
|
-
|
|
57
|
-
|
|
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(
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
83
|
-
const extensions
|
|
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
|
-
...
|
|
133
|
+
...baseConfig
|
|
98
134
|
});
|
|
99
135
|
this.includeRadiant = includeRadiant;
|
|
100
136
|
this.runtimeBundleService = new JsxRuntimeBundleService({ radiant: includeRadiant });
|
|
101
|
-
this.mdxEnabled =
|
|
137
|
+
this.mdxEnabled = mdxEnabled;
|
|
102
138
|
this.mdxExtensions = mdxExtensions;
|
|
103
|
-
|
|
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.
|
|
159
|
+
await this.registerMdxBunPlugin();
|
|
148
160
|
}
|
|
149
|
-
await this.
|
|
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
|
-
|
|
159
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
182
|
-
const scriptFiles = await this.
|
|
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.
|
|
207
|
+
const tagNames = await this.extractCustomElementTagNames(scriptFile);
|
|
185
208
|
if (tagNames.length === 0) {
|
|
186
209
|
continue;
|
|
187
210
|
}
|
|
188
|
-
const
|
|
189
|
-
if (!
|
|
211
|
+
const asset = await this.resolveCustomElementAsset(scriptFile);
|
|
212
|
+
if (!asset) {
|
|
190
213
|
continue;
|
|
191
214
|
}
|
|
192
215
|
for (const tagName of tagNames) {
|
|
193
|
-
this.
|
|
216
|
+
this.customElementAssets.set(tagName, [asset]);
|
|
194
217
|
}
|
|
195
218
|
}
|
|
196
219
|
}
|
|
197
|
-
async
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
for (const
|
|
201
|
-
const entryPath = path.join(directory,
|
|
202
|
-
if (
|
|
203
|
-
|
|
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(
|
|
207
|
-
|
|
229
|
+
if (/\.script\.(?:ts|tsx)$/.test(entry.name)) {
|
|
230
|
+
scripts.push(entryPath);
|
|
208
231
|
}
|
|
209
232
|
}
|
|
210
|
-
return
|
|
233
|
+
return scripts;
|
|
211
234
|
}
|
|
212
|
-
async
|
|
235
|
+
async resolveCustomElementAsset(scriptFile) {
|
|
213
236
|
if (!this.assetProcessingService) {
|
|
214
237
|
return void 0;
|
|
215
238
|
}
|
|
216
|
-
const [
|
|
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}:
|
|
250
|
+
`${this.name}:custom-elements:${scriptFile}`
|
|
224
251
|
);
|
|
225
|
-
return
|
|
252
|
+
return asset;
|
|
226
253
|
}
|
|
227
|
-
async
|
|
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
|
|
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:
|
|
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.
|
|
185
|
+
modulePath: this.resolvePackageExportModulePath(
|
|
186
|
+
packageDir,
|
|
187
|
+
exportKey,
|
|
188
|
+
exportTarget
|
|
189
|
+
)
|
|
163
190
|
})).filter((module) => existsSync(module.modulePath));
|
|
164
191
|
}
|
|
165
|
-
|
|
192
|
+
resolvePackageExportModulePath(packageDir, exportKey, exportTarget) {
|
|
166
193
|
if (typeof exportTarget === "string") {
|
|
167
194
|
return path.resolve(packageDir, exportTarget);
|
|
168
195
|
}
|