@ecopages/ecopages-jsx 0.2.0-alpha.22 → 0.2.0-alpha.25
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 +6 -1
- package/README.md +3 -3
- package/package.json +4 -4
- package/src/ecopages-jsx-renderer.d.ts +14 -0
- package/src/ecopages-jsx-renderer.js +127 -5
- package/src/ecopages-jsx.plugin.d.ts +1 -0
- package/src/ecopages-jsx.plugin.js +4 -0
- package/src/ecopages-jsx.types.d.ts +5 -3
- package/src/services/jsx-runtime-bundle.service.d.ts +1 -4
- package/src/services/jsx-runtime-bundle.service.js +43 -79
package/CHANGELOG.md
CHANGED
|
@@ -8,9 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
### Bug Fixes
|
|
10
10
|
|
|
11
|
-
-
|
|
11
|
+
- Fixed lazy Ecopages JSX custom-element dependencies to stay as standalone assets instead of being folded into page-owned bundles, restoring trigger-driven loading for docs components like `theme-toggle`.
|
|
12
|
+
- Fixed Ecopages JSX page-owned browser bundles to inline their JSX and Radiant runtime imports while skipping separate intrinsic custom-element script tags when the current component tree already imports those scripts.
|
|
13
|
+
- Fixed intrinsic custom-element script suppression to honor dependency-declared script ownership instead of relying only on source import scanning.
|
|
14
|
+
- Aligned the Ecopages JSX browser runtime bundle with the upstream `@ecopages/jsx` runtime shipped by current alpha releases.
|
|
12
15
|
- Aligned Ecopages JSX peer dependency ranges with the current `@ecopages/jsx` and `@ecopages/radiant` alpha releases.
|
|
13
16
|
- Aligned Radiant SSR and hydration wiring with the public `@ecopages/radiant/server/render-component` and `@ecopages/radiant/client/hydrator` entrypoints so JSX apps install an explicit client hydrator bootstrap instead of relying on implicit side effects.
|
|
17
|
+
- Updated the Ecopages JSX Radiant browser runtime for the `RadiantElement` and `RadiantController` API surface and switched the explicit hydrator bootstrap to `@ecopages/radiant/client/install-hydrator`.
|
|
14
18
|
- Fixed Radiant SSR page inspection to install the light-DOM shim before JSX page modules are imported outside the normal render pass.
|
|
15
19
|
- Restored direct `EcopagesJsxPlugin` construction so the exported class still accepts the public plugin options shape.
|
|
16
20
|
- Fixed intrinsic custom-element asset discovery so Ecopages JSX registers scripts declared with decorator and function-call `customElement(...)` syntax.
|
|
@@ -20,6 +24,7 @@
|
|
|
20
24
|
|
|
21
25
|
### Refactoring
|
|
22
26
|
|
|
27
|
+
- Removed the JSX browser import-map asset and folded the Radiant install-hydrator entry into the emitted Radiant vendor bundle.
|
|
23
28
|
- 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.
|
|
24
29
|
|
|
25
30
|
### Tests
|
package/README.md
CHANGED
|
@@ -48,9 +48,9 @@ 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
|
-
- Radiant host hydration is activated on the client through an explicit head bootstrap that
|
|
51
|
+
- 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
52
|
|
|
53
|
-
That means server-rendered `
|
|
53
|
+
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.
|
|
54
54
|
|
|
55
55
|
```ts
|
|
56
56
|
ecopagesJsxPlugin({
|
|
@@ -60,7 +60,7 @@ ecopagesJsxPlugin({
|
|
|
60
60
|
|
|
61
61
|
Set `radiant: false` when your JSX pages do not need Radiant SSR or the Radiant browser runtime on a given app.
|
|
62
62
|
|
|
63
|
-
The plugin bootstrap is intentionally explicit rather than
|
|
63
|
+
The plugin bootstrap is intentionally explicit rather than depending on custom-element modules to install the Radiant hydrator opportunistically.
|
|
64
64
|
|
|
65
65
|
## MDX Support
|
|
66
66
|
|
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.25",
|
|
4
4
|
"description": "JSX integration plugin for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"vfile": "^6.0.3"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"@ecopages/core": "0.2.0-alpha.
|
|
25
|
-
"@ecopages/jsx": "0.3.0-alpha.
|
|
26
|
-
"@ecopages/radiant": "0.3.0-alpha.
|
|
24
|
+
"@ecopages/core": "0.2.0-alpha.25",
|
|
25
|
+
"@ecopages/jsx": "0.3.0-alpha.10",
|
|
26
|
+
"@ecopages/radiant": "0.3.0-alpha.10"
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -17,8 +17,11 @@ type MdxPageModule = EcoPageFile<{
|
|
|
17
17
|
export declare class EcopagesJsxRenderer extends IntegrationRenderer<JsxRenderable> {
|
|
18
18
|
name: string;
|
|
19
19
|
private static radiantServerRuntimeInstallPromise;
|
|
20
|
+
private static readonly SCRIPT_IMPORT_RE;
|
|
20
21
|
private readonly intrinsicCustomElementAssets;
|
|
22
|
+
private readonly intrinsicCustomElementScriptFiles;
|
|
21
23
|
private collectedAssetFrames;
|
|
24
|
+
private importedIntrinsicScriptFrames;
|
|
22
25
|
private readonly mdxExtensions;
|
|
23
26
|
private readonly radiantSsrEnabled;
|
|
24
27
|
/**
|
|
@@ -57,6 +60,17 @@ export declare class EcopagesJsxRenderer extends IntegrationRenderer<JsxRenderab
|
|
|
57
60
|
private renderJsx;
|
|
58
61
|
private renderEcoComponent;
|
|
59
62
|
private recordCollectedAssets;
|
|
63
|
+
private beginImportedIntrinsicScriptFrame;
|
|
64
|
+
private endImportedIntrinsicScriptFrame;
|
|
65
|
+
private getActiveImportedIntrinsicScriptFiles;
|
|
66
|
+
/**
|
|
67
|
+
* Collects intrinsic custom-element script files already owned by the current
|
|
68
|
+
* component tree through direct source imports or dependency declarations.
|
|
69
|
+
*/
|
|
70
|
+
private collectImportedIntrinsicScriptFiles;
|
|
71
|
+
private extractImportedIntrinsicScriptFiles;
|
|
72
|
+
private extractConfiguredDependencyScriptFiles;
|
|
73
|
+
private resolveImportedIntrinsicScriptFile;
|
|
60
74
|
private ensureRadiantServerRuntimeIfEnabled;
|
|
61
75
|
private ensureRadiantServerRuntimeInstalled;
|
|
62
76
|
private isFunctionComponent;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { rapidhash } from "@ecopages/core/hash";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import {
|
|
3
5
|
IntegrationRenderer
|
|
4
6
|
} from "@ecopages/core/route-renderer/integration-renderer";
|
|
@@ -8,8 +10,11 @@ import { ECOPAGES_JSX_PLUGIN_NAME } from "./ecopages-jsx.constants.js";
|
|
|
8
10
|
class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
9
11
|
name = ECOPAGES_JSX_PLUGIN_NAME;
|
|
10
12
|
static radiantServerRuntimeInstallPromise;
|
|
13
|
+
static SCRIPT_IMPORT_RE = /import\s+(?:[^'";]+\s+from\s+)?['"](\.[^'"\n]*\.script(?:\.[cm]?[jt]sx?)?)['"]/g;
|
|
11
14
|
intrinsicCustomElementAssets;
|
|
15
|
+
intrinsicCustomElementScriptFiles;
|
|
12
16
|
collectedAssetFrames = [];
|
|
17
|
+
importedIntrinsicScriptFrames = [];
|
|
13
18
|
mdxExtensions;
|
|
14
19
|
radiantSsrEnabled;
|
|
15
20
|
/**
|
|
@@ -63,6 +68,7 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
63
68
|
runtimeOrigin
|
|
64
69
|
});
|
|
65
70
|
this.intrinsicCustomElementAssets = jsxConfig?.intrinsicCustomElementAssets ?? /* @__PURE__ */ new Map();
|
|
71
|
+
this.intrinsicCustomElementScriptFiles = jsxConfig?.intrinsicCustomElementScriptFiles ?? /* @__PURE__ */ new Map();
|
|
66
72
|
this.mdxExtensions = jsxConfig?.mdxExtensions ?? [".mdx"];
|
|
67
73
|
this.radiantSsrEnabled = jsxConfig?.radiantSsrEnabled ?? false;
|
|
68
74
|
}
|
|
@@ -76,6 +82,11 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
76
82
|
return this.isMdxFile(file) ? this.normalizeMdxPageModule(file, module) : module;
|
|
77
83
|
}
|
|
78
84
|
async render(options) {
|
|
85
|
+
const importedScriptFrame = this.beginImportedIntrinsicScriptFrame([
|
|
86
|
+
options.Page,
|
|
87
|
+
options.Layout,
|
|
88
|
+
options.HtmlTemplate
|
|
89
|
+
]);
|
|
79
90
|
try {
|
|
80
91
|
return await this.renderPageWithDocumentShell({
|
|
81
92
|
page: {
|
|
@@ -98,18 +109,18 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
98
109
|
});
|
|
99
110
|
} catch (error) {
|
|
100
111
|
throw this.createRenderError("Error rendering page", error);
|
|
112
|
+
} finally {
|
|
113
|
+
this.endImportedIntrinsicScriptFrame(importedScriptFrame);
|
|
101
114
|
}
|
|
102
115
|
}
|
|
103
116
|
async renderComponent(input) {
|
|
117
|
+
const importedScriptFrame = this.beginImportedIntrinsicScriptFrame([input.component]);
|
|
104
118
|
const assetFrame = this.beginCollectedAssetFrame();
|
|
105
119
|
try {
|
|
106
120
|
if (!this.isFunctionComponent(input.component)) {
|
|
107
121
|
throw new TypeError("JSX renderer expected a callable component.");
|
|
108
122
|
}
|
|
109
|
-
const content = await this.renderEcoComponent(
|
|
110
|
-
input.component,
|
|
111
|
-
this.createComponentProps(input)
|
|
112
|
-
);
|
|
123
|
+
const content = await this.renderEcoComponent(input.component, this.createComponentProps(input));
|
|
113
124
|
const rendered = await this.renderJsx(content);
|
|
114
125
|
const queuedBoundaryResolution = await this.resolveOwnedBoundaryHtml(
|
|
115
126
|
rendered.html,
|
|
@@ -131,6 +142,8 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
131
142
|
} catch (error) {
|
|
132
143
|
this.endCollectedAssetFrame(assetFrame);
|
|
133
144
|
throw this.createRenderError("Error rendering component", error);
|
|
145
|
+
} finally {
|
|
146
|
+
this.endImportedIntrinsicScriptFrame(importedScriptFrame);
|
|
134
147
|
}
|
|
135
148
|
}
|
|
136
149
|
createComponentBoundaryRuntime(options) {
|
|
@@ -140,6 +153,7 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
140
153
|
});
|
|
141
154
|
}
|
|
142
155
|
async renderToResponse(view, props, ctx) {
|
|
156
|
+
const importedScriptFrame = this.beginImportedIntrinsicScriptFrame([view, view.config?.layout]);
|
|
143
157
|
try {
|
|
144
158
|
if (!this.isFunctionComponent(view)) {
|
|
145
159
|
throw new TypeError("JSX renderer expected a callable view component.");
|
|
@@ -152,6 +166,8 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
152
166
|
});
|
|
153
167
|
} catch (error) {
|
|
154
168
|
throw this.createRenderError("Error rendering view", error);
|
|
169
|
+
} finally {
|
|
170
|
+
this.endImportedIntrinsicScriptFrame(importedScriptFrame);
|
|
155
171
|
}
|
|
156
172
|
}
|
|
157
173
|
/**
|
|
@@ -162,6 +178,9 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
162
178
|
* any layout or document-shell logic runs.
|
|
163
179
|
*/
|
|
164
180
|
normalizeMdxPageModule(file, module) {
|
|
181
|
+
if (!this.isFunctionComponent(module.default)) {
|
|
182
|
+
throw new TypeError("MDX file must export a callable default component.");
|
|
183
|
+
}
|
|
165
184
|
const Page = module.default;
|
|
166
185
|
const normalizedConfig = {
|
|
167
186
|
...module.config ?? Page.config ?? {},
|
|
@@ -221,6 +240,104 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
221
240
|
}
|
|
222
241
|
return dedupedAssets;
|
|
223
242
|
}
|
|
243
|
+
beginImportedIntrinsicScriptFrame(components) {
|
|
244
|
+
const frame = this.collectImportedIntrinsicScriptFiles(components);
|
|
245
|
+
this.importedIntrinsicScriptFrames.push(frame);
|
|
246
|
+
return frame;
|
|
247
|
+
}
|
|
248
|
+
endImportedIntrinsicScriptFrame(frame) {
|
|
249
|
+
const activeFrame = this.importedIntrinsicScriptFrames.pop();
|
|
250
|
+
if (activeFrame !== frame) {
|
|
251
|
+
this.importedIntrinsicScriptFrames = this.importedIntrinsicScriptFrames.filter((entry) => entry !== frame);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
getActiveImportedIntrinsicScriptFiles() {
|
|
255
|
+
return this.importedIntrinsicScriptFrames[this.importedIntrinsicScriptFrames.length - 1];
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Collects intrinsic custom-element script files already owned by the current
|
|
259
|
+
* component tree through direct source imports or dependency declarations.
|
|
260
|
+
*/
|
|
261
|
+
collectImportedIntrinsicScriptFiles(components) {
|
|
262
|
+
const importedScriptFiles = /* @__PURE__ */ new Set();
|
|
263
|
+
const visitedFiles = /* @__PURE__ */ new Set();
|
|
264
|
+
const visit = (component) => {
|
|
265
|
+
const file = component?.config?.__eco?.file;
|
|
266
|
+
if (!file || visitedFiles.has(file)) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
visitedFiles.add(file);
|
|
270
|
+
for (const scriptFile of this.extractConfiguredDependencyScriptFiles(component, path.dirname(file))) {
|
|
271
|
+
importedScriptFiles.add(scriptFile);
|
|
272
|
+
}
|
|
273
|
+
for (const scriptFile of this.extractImportedIntrinsicScriptFiles(file)) {
|
|
274
|
+
importedScriptFiles.add(scriptFile);
|
|
275
|
+
}
|
|
276
|
+
for (const nestedComponent of component?.config?.dependencies?.components ?? []) {
|
|
277
|
+
visit(nestedComponent);
|
|
278
|
+
}
|
|
279
|
+
visit(component?.config?.layout);
|
|
280
|
+
};
|
|
281
|
+
for (const component of components) {
|
|
282
|
+
visit(component);
|
|
283
|
+
}
|
|
284
|
+
return importedScriptFiles;
|
|
285
|
+
}
|
|
286
|
+
extractImportedIntrinsicScriptFiles(file) {
|
|
287
|
+
let source;
|
|
288
|
+
try {
|
|
289
|
+
source = readFileSync(file, "utf8");
|
|
290
|
+
} catch {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
const scriptFiles = /* @__PURE__ */ new Set();
|
|
294
|
+
const directory = path.dirname(file);
|
|
295
|
+
for (const match of source.matchAll(EcopagesJsxRenderer.SCRIPT_IMPORT_RE)) {
|
|
296
|
+
const specifier = match[1];
|
|
297
|
+
if (!specifier) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const resolvedScriptFile = this.resolveImportedIntrinsicScriptFile(directory, specifier);
|
|
301
|
+
if (resolvedScriptFile) {
|
|
302
|
+
scriptFiles.add(resolvedScriptFile);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return [...scriptFiles];
|
|
306
|
+
}
|
|
307
|
+
extractConfiguredDependencyScriptFiles(component, directory) {
|
|
308
|
+
const scriptFiles = /* @__PURE__ */ new Set();
|
|
309
|
+
for (const script of component?.config?.dependencies?.scripts ?? []) {
|
|
310
|
+
const specifier = typeof script === "string" ? script : script.src;
|
|
311
|
+
if (!specifier) {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const resolvedScriptFile = this.resolveImportedIntrinsicScriptFile(directory, specifier);
|
|
315
|
+
if (resolvedScriptFile) {
|
|
316
|
+
scriptFiles.add(resolvedScriptFile);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return [...scriptFiles];
|
|
320
|
+
}
|
|
321
|
+
resolveImportedIntrinsicScriptFile(directory, specifier) {
|
|
322
|
+
const basePath = path.resolve(directory, specifier);
|
|
323
|
+
const candidatePaths = [
|
|
324
|
+
basePath,
|
|
325
|
+
`${basePath}.ts`,
|
|
326
|
+
`${basePath}.tsx`,
|
|
327
|
+
`${basePath}.js`,
|
|
328
|
+
`${basePath}.jsx`,
|
|
329
|
+
`${basePath}.mts`,
|
|
330
|
+
`${basePath}.cts`,
|
|
331
|
+
`${basePath}.mjs`,
|
|
332
|
+
`${basePath}.cjs`
|
|
333
|
+
];
|
|
334
|
+
for (const candidatePath of candidatePaths) {
|
|
335
|
+
if (existsSync(candidatePath)) {
|
|
336
|
+
return candidatePath;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return void 0;
|
|
340
|
+
}
|
|
224
341
|
async ensureRadiantServerRuntimeIfEnabled() {
|
|
225
342
|
if (!this.radiantSsrEnabled) {
|
|
226
343
|
return;
|
|
@@ -270,7 +387,7 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
270
387
|
config,
|
|
271
388
|
metadata
|
|
272
389
|
}) {
|
|
273
|
-
const wrappedPage =
|
|
390
|
+
const wrappedPage = async (props) => await this.invokeComponent(page, props);
|
|
274
391
|
wrappedPage.config = config;
|
|
275
392
|
if (metadata) {
|
|
276
393
|
wrappedPage.metadata = metadata;
|
|
@@ -279,6 +396,11 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
279
396
|
}
|
|
280
397
|
createIntrinsicCustomElementRenderHook(target) {
|
|
281
398
|
return ({ tagName }) => {
|
|
399
|
+
const currentImportedScriptFiles = this.getActiveImportedIntrinsicScriptFiles();
|
|
400
|
+
const intrinsicScriptFile = this.intrinsicCustomElementScriptFiles.get(tagName);
|
|
401
|
+
if (intrinsicScriptFile && currentImportedScriptFiles?.has(intrinsicScriptFile)) {
|
|
402
|
+
return void 0;
|
|
403
|
+
}
|
|
282
404
|
const assets = this.intrinsicCustomElementAssets.get(tagName);
|
|
283
405
|
if (assets) {
|
|
284
406
|
target.push(...assets);
|
|
@@ -8,6 +8,7 @@ export type { EcopagesJsxMdxCompileOptions, EcopagesJsxMdxOptions, EcopagesJsxPl
|
|
|
8
8
|
export declare class EcopagesJsxPlugin extends IntegrationPlugin<JsxRenderable> {
|
|
9
9
|
renderer: typeof EcopagesJsxRenderer;
|
|
10
10
|
private customElementAssets;
|
|
11
|
+
private customElementScriptFiles;
|
|
11
12
|
private includeRadiant;
|
|
12
13
|
private mdxEnabled;
|
|
13
14
|
private mdxCompilerOptions?;
|
|
@@ -80,6 +80,7 @@ const resolvePluginOptions = (options) => {
|
|
|
80
80
|
class EcopagesJsxPlugin extends IntegrationPlugin {
|
|
81
81
|
renderer = EcopagesJsxRenderer;
|
|
82
82
|
customElementAssets = /* @__PURE__ */ new Map();
|
|
83
|
+
customElementScriptFiles = /* @__PURE__ */ new Map();
|
|
83
84
|
includeRadiant;
|
|
84
85
|
mdxEnabled;
|
|
85
86
|
mdxCompilerOptions;
|
|
@@ -112,6 +113,7 @@ class EcopagesJsxPlugin extends IntegrationPlugin {
|
|
|
112
113
|
initializeRenderer(options) {
|
|
113
114
|
const rendererConfig = {
|
|
114
115
|
intrinsicCustomElementAssets: this.customElementAssets,
|
|
116
|
+
intrinsicCustomElementScriptFiles: this.customElementScriptFiles,
|
|
115
117
|
mdxExtensions: this.mdxExtensions,
|
|
116
118
|
radiantSsrEnabled: this.includeRadiant
|
|
117
119
|
};
|
|
@@ -200,6 +202,7 @@ class EcopagesJsxPlugin extends IntegrationPlugin {
|
|
|
200
202
|
return;
|
|
201
203
|
}
|
|
202
204
|
this.customElementAssets.clear();
|
|
205
|
+
this.customElementScriptFiles.clear();
|
|
203
206
|
const scriptFiles = await this.collectScriptEntries(this.appConfig.absolutePaths.srcDir);
|
|
204
207
|
for (const scriptFile of scriptFiles) {
|
|
205
208
|
const tagNames = await this.extractCustomElementTagNames(scriptFile);
|
|
@@ -212,6 +215,7 @@ class EcopagesJsxPlugin extends IntegrationPlugin {
|
|
|
212
215
|
}
|
|
213
216
|
for (const tagName of tagNames) {
|
|
214
217
|
this.customElementAssets.set(tagName, [asset]);
|
|
218
|
+
this.customElementScriptFiles.set(tagName, scriptFile);
|
|
215
219
|
}
|
|
216
220
|
}
|
|
217
221
|
}
|
|
@@ -37,9 +37,10 @@ export type EcopagesJsxPluginOptions = Omit<IntegrationPluginConfig, 'name' | 'e
|
|
|
37
37
|
*
|
|
38
38
|
* When enabled, Ecopages JSX:
|
|
39
39
|
* - imports `@ecopages/radiant/server/render-component` before Radiant SSR
|
|
40
|
-
* -
|
|
41
|
-
* -
|
|
42
|
-
*
|
|
40
|
+
* - rewrites browser runtime specifiers to emitted vendor assets at build time
|
|
41
|
+
* - folds `@ecopages/radiant/client/install-hydrator` into the emitted
|
|
42
|
+
* Radiant vendor so intrinsic custom-element modules install the
|
|
43
|
+
* hydrator before they connect
|
|
43
44
|
*
|
|
44
45
|
* Set to `false` when pages do not use Radiant web components.
|
|
45
46
|
* @default true
|
|
@@ -50,6 +51,7 @@ export type EcopagesJsxPluginOptions = Omit<IntegrationPluginConfig, 'name' | 'e
|
|
|
50
51
|
};
|
|
51
52
|
export type EcopagesJsxRendererConfig = {
|
|
52
53
|
intrinsicCustomElementAssets?: Map<string, readonly ProcessedAsset[]>;
|
|
54
|
+
intrinsicCustomElementScriptFiles?: Map<string, string>;
|
|
53
55
|
mdxExtensions?: string[];
|
|
54
56
|
radiantSsrEnabled?: boolean;
|
|
55
57
|
};
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
|
|
12
12
|
import { type AssetDefinition } from '@ecopages/core/services/asset-processing-service';
|
|
13
|
-
export declare const RADIANT_HYDRATOR_BOOTSTRAP_ATTRIBUTE = "data-ecopages-jsx-radiant-hydrator";
|
|
14
13
|
export interface JsxRuntimeBundleServiceConfig {
|
|
15
14
|
radiant: boolean;
|
|
16
15
|
rootDir?: string;
|
|
@@ -42,11 +41,9 @@ export declare class JsxRuntimeBundleService {
|
|
|
42
41
|
getSpecifierMap(): Promise<Record<string, string>>;
|
|
43
42
|
/**
|
|
44
43
|
* Builds the full list of vendor asset definitions: the import map inline
|
|
45
|
-
*
|
|
44
|
+
* assets plus one `createBrowserRuntimeScriptAsset` per vendor bundle.
|
|
46
45
|
*/
|
|
47
46
|
getDependencies(): Promise<AssetDefinition[]>;
|
|
48
|
-
private createRadiantHydratorBootstrapAsset;
|
|
49
|
-
private createRadiantHydratorBootstrapSource;
|
|
50
47
|
private getArtifactsDir;
|
|
51
48
|
private getEntryImportPath;
|
|
52
49
|
private getOrCreateSpecifierMap;
|
|
@@ -2,27 +2,13 @@ import path from "node:path";
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { createRuntimeSpecifierAliasPlugin } from "@ecopages/core/build/runtime-specifier-alias-plugin";
|
|
4
4
|
import {
|
|
5
|
-
BROWSER_RUNTIME_SCRIPT_ATTRIBUTES,
|
|
6
5
|
buildBrowserRuntimeAssetUrl,
|
|
7
|
-
createBrowserRuntimeScriptAsset
|
|
8
|
-
AssetFactory
|
|
6
|
+
createBrowserRuntimeScriptAsset
|
|
9
7
|
} from "@ecopages/core/services/asset-processing-service";
|
|
10
8
|
const VENDOR_FILE_NAMES = {
|
|
11
9
|
jsx: "ecopages-jsx-esm.js",
|
|
12
10
|
radiant: "ecopages-radiant-esm.js"
|
|
13
11
|
};
|
|
14
|
-
const RADIANT_HYDRATOR_BOOTSTRAP_ATTRIBUTE = "data-ecopages-jsx-radiant-hydrator";
|
|
15
|
-
const JSX_RUNTIME_NAMESPACE_REPAIR_SNIPPET = "function rG(W,G,J){let j=G instanceof Element?G:G?.parentElement,U=j?.namespaceURI??K9,$=j?.localName,X=W.firstElementChild;if(!X)return;let F=J??X.localName,Z=O9(U,$,F);if(X.namespaceURI===Z&&X.localName===F)return;W.replaceChild(tG(X,Z,F),X)}function tG(W,G,J){let j=document.createElementNS(G,J);for(let U of Array.from(W.attributes)){if(U.namespaceURI){j.setAttributeNS(U.namespaceURI,U.name,U.value);continue}n(j,U.name,U.value)}return j.append(...W.childNodes),j}";
|
|
16
|
-
const JSX_RUNTIME_NAMESPACE_REPAIR_PATCH = [
|
|
17
|
-
"const eopHtmlNamespace='http://www.w3.org/1999/xhtml',eopSvgNamespace='http://www.w3.org/2000/svg',eopCanonicalSvgLocalNames={altglyph:'altGlyph',altglyphdef:'altGlyphDef',altglyphitem:'altGlyphItem',animatemotion:'animateMotion',animatetransform:'animateTransform',clippath:'clipPath',feblend:'feBlend',fecolormatrix:'feColorMatrix',fecomponenttransfer:'feComponentTransfer',fecomposite:'feComposite',feconvolvematrix:'feConvolveMatrix',fediffuselighting:'feDiffuseLighting',fedisplacementmap:'feDisplacementMap',fedistantlight:'feDistantLight',fedropshadow:'feDropShadow',feflood:'feFlood',fefunca:'feFuncA',fefuncb:'feFuncB',fefuncg:'feFuncG',fefuncr:'feFuncR',fegaussianblur:'feGaussianBlur',feimage:'feImage',femerge:'feMerge',femergenode:'feMergeNode',femorphology:'feMorphology',feoffset:'feOffset',fepointlight:'fePointLight',fespecularlighting:'feSpecularLighting',fespotlight:'feSpotLight',fetile:'feTile',feturbulence:'feTurbulence',foreignobject:'foreignObject',glyphref:'glyphRef',lineargradient:'linearGradient',radialgradient:'radialGradient',textpath:'textPath'};",
|
|
18
|
-
"function eopGetCanonicalSvgLocalName(W){return eopCanonicalSvgLocalNames[W]??W}",
|
|
19
|
-
"function eopIsSvgNamespace(W){return W===eopSvgNamespace}",
|
|
20
|
-
"function rG(W,G,J){let j=G instanceof Element?G:G?.parentElement,U=j?.namespaceURI??K9,$=j?.localName;eopRepairNamespaceFragment(W,U??eopHtmlNamespace,$,J)}",
|
|
21
|
-
"function eopRepairNamespaceFragment(W,G,J,j){let U=W.firstElementChild;if(!U)return;let $=j??U.localName,X=O9(G,J,$),F=eopIsSvgNamespace(X)?eopGetCanonicalSvgLocalName($):$;eopRepairNamespaceElement(W,U,X,F)}",
|
|
22
|
-
"function eopRepairNamespaceElement(W,G,J,j){let U=G;if(G.namespaceURI!==J||G.localName!==j)U=tG(G,J,j),W.replaceChild(U,G);eopRepairNamespaceChildren(U,J,j)}",
|
|
23
|
-
"function eopRepairNamespaceChildren(W,G,J){for(let j of Array.from(W.children)){let U=O9(G,J,j.localName),$=eopIsSvgNamespace(U)?eopGetCanonicalSvgLocalName(j.localName):j.localName,X=j;if(j.namespaceURI!==U||j.localName!==$)X=tG(j,U,$),W.replaceChild(X,j);eopRepairNamespaceChildren(X,U,$)}}",
|
|
24
|
-
"function tG(W,G,J){let j=document.createElementNS(G,eopIsSvgNamespace(G)?eopGetCanonicalSvgLocalName(J):J);for(let U of Array.from(W.attributes)){if(U.namespaceURI){j.setAttributeNS(U.namespaceURI,U.name,U.value);continue}n(j,U.name,U.value)}return j.append(...W.childNodes),j}"
|
|
25
|
-
].join("");
|
|
26
12
|
function getNamedExportNamesFromModuleSource(source) {
|
|
27
13
|
const exportNames = /* @__PURE__ */ new Set();
|
|
28
14
|
for (const match of source.matchAll(/export\s*\{([^}]+)\}/g)) {
|
|
@@ -50,27 +36,34 @@ function isBrowserRuntimeRadiantSpecifier(exportKey) {
|
|
|
50
36
|
if (exportKey === "." || exportKey.startsWith("./context/")) {
|
|
51
37
|
return true;
|
|
52
38
|
}
|
|
39
|
+
if (exportKey === "./controller-registry") {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
53
42
|
if (exportKey === "./client/hydrator") {
|
|
54
43
|
return true;
|
|
55
44
|
}
|
|
45
|
+
if (exportKey === "./client/install-hydrator") {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
56
48
|
if (exportKey.startsWith("./decorators/") || exportKey.startsWith("./helpers/")) {
|
|
57
49
|
return true;
|
|
58
50
|
}
|
|
59
|
-
return exportKey === "./core/radiant-
|
|
51
|
+
return exportKey === "./core/radiant-controller" || exportKey === "./core/radiant-element";
|
|
60
52
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
throw new Error(`Could not find ${label} in @ecopages/jsx browser runtime source`);
|
|
64
|
-
}
|
|
65
|
-
return source.replace(search, replacement);
|
|
53
|
+
function isObjectRecord(value) {
|
|
54
|
+
return typeof value === "object" && value !== null;
|
|
66
55
|
}
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
56
|
+
function readRadiantPackageJson(manifestPath) {
|
|
57
|
+
const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
58
|
+
if (!isObjectRecord(parsed)) {
|
|
59
|
+
throw new Error(`Invalid package manifest at ${manifestPath}`);
|
|
60
|
+
}
|
|
61
|
+
if (parsed.exports !== void 0 && !isObjectRecord(parsed.exports)) {
|
|
62
|
+
throw new Error(`Invalid package exports in ${manifestPath}`);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
exports: parsed.exports
|
|
66
|
+
};
|
|
74
67
|
}
|
|
75
68
|
function findPackageManifestPath(packageName) {
|
|
76
69
|
let currentDir = path.dirname(new URL(import.meta.url).pathname);
|
|
@@ -124,22 +117,11 @@ class JsxRuntimeBundleService {
|
|
|
124
117
|
}
|
|
125
118
|
/**
|
|
126
119
|
* Builds the full list of vendor asset definitions: the import map inline
|
|
127
|
-
*
|
|
120
|
+
* assets plus one `createBrowserRuntimeScriptAsset` per vendor bundle.
|
|
128
121
|
*/
|
|
129
122
|
async getDependencies() {
|
|
130
|
-
const specifierMap = await this.getSpecifierMap();
|
|
131
123
|
const jsxEntryModulePath = await this.getOrCreateJsxEntryModulePath();
|
|
132
|
-
const deps = [
|
|
133
|
-
AssetFactory.createInlineContentScript({
|
|
134
|
-
position: "head",
|
|
135
|
-
bundle: false,
|
|
136
|
-
content: JSON.stringify({ imports: specifierMap }, null, 2),
|
|
137
|
-
attributes: { type: "importmap" }
|
|
138
|
-
})
|
|
139
|
-
];
|
|
140
|
-
if (this.config.radiant) {
|
|
141
|
-
deps.push(this.createRadiantHydratorBootstrapAsset());
|
|
142
|
-
}
|
|
124
|
+
const deps = [];
|
|
143
125
|
deps.push(
|
|
144
126
|
createBrowserRuntimeScriptAsset({
|
|
145
127
|
importPath: jsxEntryModulePath,
|
|
@@ -159,23 +141,6 @@ class JsxRuntimeBundleService {
|
|
|
159
141
|
}
|
|
160
142
|
return deps;
|
|
161
143
|
}
|
|
162
|
-
createRadiantHydratorBootstrapAsset() {
|
|
163
|
-
return AssetFactory.createInlineContentScript({
|
|
164
|
-
position: "head",
|
|
165
|
-
bundle: false,
|
|
166
|
-
content: this.createRadiantHydratorBootstrapSource(),
|
|
167
|
-
attributes: {
|
|
168
|
-
...BROWSER_RUNTIME_SCRIPT_ATTRIBUTES,
|
|
169
|
-
[RADIANT_HYDRATOR_BOOTSTRAP_ATTRIBUTE]: "true"
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
createRadiantHydratorBootstrapSource() {
|
|
174
|
-
return [
|
|
175
|
-
"import { installRadiantHydrator } from '@ecopages/radiant/client/hydrator';",
|
|
176
|
-
"installRadiantHydrator();"
|
|
177
|
-
].join("\n");
|
|
178
|
-
}
|
|
179
144
|
getArtifactsDir() {
|
|
180
145
|
const rootDir = this.config.rootDir ?? process.cwd();
|
|
181
146
|
return path.join(rootDir, "node_modules", ".cache", "ecopages-browser-runtime");
|
|
@@ -197,9 +162,7 @@ class JsxRuntimeBundleService {
|
|
|
197
162
|
};
|
|
198
163
|
if (this.config.radiant) {
|
|
199
164
|
const radiantVendorUrl = buildBrowserRuntimeAssetUrl(VENDOR_FILE_NAMES.radiant);
|
|
200
|
-
const radiantPkg =
|
|
201
|
-
readFileSync(findPackageManifestPath("@ecopages/radiant"), "utf8")
|
|
202
|
-
);
|
|
165
|
+
const radiantPkg = readRadiantPackageJson(findPackageManifestPath("@ecopages/radiant"));
|
|
203
166
|
for (const key of Object.keys(radiantPkg.exports ?? {})) {
|
|
204
167
|
if (!isBrowserRuntimeRadiantSpecifier(key)) {
|
|
205
168
|
continue;
|
|
@@ -219,36 +182,28 @@ class JsxRuntimeBundleService {
|
|
|
219
182
|
const filePath = path.join(artifactsDir, "ecopages-jsx-esm-entry.mjs");
|
|
220
183
|
const manifestPath = findPackageManifestPath("@ecopages/jsx");
|
|
221
184
|
const packageDir = path.dirname(realpathSync(manifestPath));
|
|
222
|
-
const jsxPkg =
|
|
223
|
-
const jsxModulePath = this.resolvePackageExportModulePath(
|
|
224
|
-
|
|
225
|
-
".",
|
|
226
|
-
jsxPkg.exports?.["."]
|
|
227
|
-
);
|
|
228
|
-
const patchedRuntimeSource = createPatchedJsxBrowserRuntimeSource(readFileSync(jsxModulePath, "utf8"));
|
|
185
|
+
const jsxPkg = readRadiantPackageJson(manifestPath);
|
|
186
|
+
const jsxModulePath = this.resolvePackageExportModulePath(packageDir, ".", jsxPkg.exports?.["."]);
|
|
187
|
+
const jsxRuntimeSource = readFileSync(jsxModulePath, "utf8");
|
|
229
188
|
mkdirSync(artifactsDir, { recursive: true });
|
|
230
|
-
writeFileSync(filePath,
|
|
189
|
+
writeFileSync(filePath, jsxRuntimeSource, "utf8");
|
|
231
190
|
this.cachedJsxEntryModulePath = filePath;
|
|
232
191
|
return filePath;
|
|
233
192
|
}
|
|
234
193
|
getRadiantBrowserRuntimeModules() {
|
|
235
194
|
const manifestPath = findPackageManifestPath("@ecopages/radiant");
|
|
236
195
|
const packageDir = path.dirname(realpathSync(manifestPath));
|
|
237
|
-
const radiantPkg =
|
|
196
|
+
const radiantPkg = readRadiantPackageJson(manifestPath);
|
|
238
197
|
return Object.entries(radiantPkg.exports ?? {}).filter(([key]) => isBrowserRuntimeRadiantSpecifier(key) && key !== ".").sort(([left], [right]) => left.localeCompare(right)).map(([exportKey, exportTarget]) => ({
|
|
239
198
|
exportKey,
|
|
240
|
-
modulePath: this.resolvePackageExportModulePath(
|
|
241
|
-
packageDir,
|
|
242
|
-
exportKey,
|
|
243
|
-
exportTarget
|
|
244
|
-
)
|
|
199
|
+
modulePath: this.resolvePackageExportModulePath(packageDir, exportKey, exportTarget)
|
|
245
200
|
})).filter((module) => existsSync(module.modulePath));
|
|
246
201
|
}
|
|
247
202
|
resolvePackageExportModulePath(packageDir, exportKey, exportTarget) {
|
|
248
203
|
if (typeof exportTarget === "string") {
|
|
249
204
|
return path.resolve(packageDir, exportTarget);
|
|
250
205
|
}
|
|
251
|
-
if (exportTarget &&
|
|
206
|
+
if (isObjectRecord(exportTarget) && "import" in exportTarget) {
|
|
252
207
|
const importTarget = exportTarget.import;
|
|
253
208
|
if (typeof importTarget === "string") {
|
|
254
209
|
return path.resolve(packageDir, importTarget);
|
|
@@ -262,8 +217,18 @@ class JsxRuntimeBundleService {
|
|
|
262
217
|
}
|
|
263
218
|
const artifactsDir = this.getArtifactsDir();
|
|
264
219
|
const filePath = path.join(artifactsDir, "ecopages-radiant-esm-entry.mjs");
|
|
220
|
+
const manifestPath = findPackageManifestPath("@ecopages/radiant");
|
|
221
|
+
const packageDir = path.dirname(realpathSync(manifestPath));
|
|
222
|
+
const radiantPkg = readRadiantPackageJson(manifestPath);
|
|
223
|
+
const installHydratorModulePath = this.resolvePackageExportModulePath(
|
|
224
|
+
packageDir,
|
|
225
|
+
"./client/install-hydrator",
|
|
226
|
+
radiantPkg.exports?.["./client/install-hydrator"]
|
|
227
|
+
);
|
|
265
228
|
const seenExports = /* @__PURE__ */ new Set();
|
|
266
|
-
const statements = [
|
|
229
|
+
const statements = [
|
|
230
|
+
`import '${this.getEntryImportPath(artifactsDir, installHydratorModulePath)}';`
|
|
231
|
+
];
|
|
267
232
|
mkdirSync(artifactsDir, { recursive: true });
|
|
268
233
|
for (const module of this.getRadiantBrowserRuntimeModules()) {
|
|
269
234
|
const exportNames = getNamedExportNamesFromModuleSource(readFileSync(module.modulePath, "utf8")).filter((name) => !seenExports.has(name)).filter((name) => /^[$A-Z_a-z][$\w]*$/.test(name)).sort();
|
|
@@ -282,6 +247,5 @@ class JsxRuntimeBundleService {
|
|
|
282
247
|
}
|
|
283
248
|
}
|
|
284
249
|
export {
|
|
285
|
-
JsxRuntimeBundleService
|
|
286
|
-
RADIANT_HYDRATOR_BOOTSTRAP_ATTRIBUTE
|
|
250
|
+
JsxRuntimeBundleService
|
|
287
251
|
};
|