@ecopages/ecopages-jsx 0.2.0-alpha.17 → 0.2.0-alpha.18
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 +1 -0
- package/README.md +11 -3
- package/package.json +4 -4
- package/src/ecopages-jsx-renderer.d.ts +4 -2
- package/src/ecopages-jsx-renderer.js +27 -23
- package/src/ecopages-jsx.types.d.ts +7 -2
- package/src/services/jsx-runtime-bundle.service.d.ts +5 -1
- package/src/services/jsx-runtime-bundle.service.js +43 -14
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
### Bug Fixes
|
|
10
10
|
|
|
11
|
+
- 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.
|
|
11
12
|
- Fixed Radiant SSR page inspection to install the light-DOM shim before JSX page modules are imported outside the normal render pass.
|
|
12
13
|
- Restored direct `EcopagesJsxPlugin` construction so the exported class still accepts the public plugin options shape.
|
|
13
14
|
- Fixed intrinsic custom-element asset discovery so Ecopages JSX registers scripts declared with decorator and function-call `customElement(...)` syntax.
|
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ export default config;
|
|
|
29
29
|
## What This Integration Owns
|
|
30
30
|
|
|
31
31
|
- `.tsx` route files by default. Use `extensions` to change the JSX route suffix list.
|
|
32
|
-
- Optional Radiant runtime
|
|
32
|
+
- Optional Radiant SSR/runtime wiring through the public `@ecopages/radiant` entrypoints.
|
|
33
33
|
- Optional `.mdx` routes compiled against the `@ecopages/jsx` runtime.
|
|
34
34
|
|
|
35
35
|
## Route Extensions
|
|
@@ -44,7 +44,13 @@ ecopagesJsxPlugin({
|
|
|
44
44
|
|
|
45
45
|
## Radiant Support
|
|
46
46
|
|
|
47
|
-
Radiant
|
|
47
|
+
Radiant support is enabled by default. When `radiant: true`, the plugin keeps the ownership split explicit:
|
|
48
|
+
|
|
49
|
+
- Ecopages JSX owns page-level JSX SSR and container hydration.
|
|
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 calls `installRadiantHydrator()` from `@ecopages/radiant/client/hydrator` before intrinsic custom-element modules load.
|
|
52
|
+
|
|
53
|
+
That means server-rendered `RadiantComponent` 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.
|
|
48
54
|
|
|
49
55
|
```ts
|
|
50
56
|
ecopagesJsxPlugin({
|
|
@@ -52,7 +58,9 @@ ecopagesJsxPlugin({
|
|
|
52
58
|
});
|
|
53
59
|
```
|
|
54
60
|
|
|
55
|
-
Set `radiant: false` when your JSX pages do not need the Radiant browser runtime on a given app.
|
|
61
|
+
Set `radiant: false` when your JSX pages do not need Radiant SSR or the Radiant browser runtime on a given app.
|
|
62
|
+
|
|
63
|
+
The plugin bootstrap is intentionally explicit rather than relying on unrelated imports to install the Radiant hydrator as a side effect.
|
|
56
64
|
|
|
57
65
|
## MDX Support
|
|
58
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.18",
|
|
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.18",
|
|
25
|
+
"@ecopages/jsx": "^0.3.0-alpha.1",
|
|
26
|
+
"@ecopages/radiant": "^0.3.0-alpha.1"
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -16,7 +16,7 @@ type MdxPageModule = EcoPageFile<{
|
|
|
16
16
|
*/
|
|
17
17
|
export declare class EcopagesJsxRenderer extends IntegrationRenderer<JsxRenderable> {
|
|
18
18
|
name: string;
|
|
19
|
-
private static
|
|
19
|
+
private static radiantServerRuntimeInstallPromise;
|
|
20
20
|
private readonly intrinsicCustomElementAssets;
|
|
21
21
|
private collectedAssetFrames;
|
|
22
22
|
private readonly mdxExtensions;
|
|
@@ -56,7 +56,9 @@ export declare class EcopagesJsxRenderer extends IntegrationRenderer<JsxRenderab
|
|
|
56
56
|
private endCollectedAssetFrame;
|
|
57
57
|
private renderJsx;
|
|
58
58
|
private renderEcoComponent;
|
|
59
|
-
private
|
|
59
|
+
private recordCollectedAssets;
|
|
60
|
+
private ensureRadiantServerRuntimeIfEnabled;
|
|
61
|
+
private ensureRadiantServerRuntimeInstalled;
|
|
60
62
|
private isFunctionComponent;
|
|
61
63
|
private createComponentProps;
|
|
62
64
|
private collectComponentAssets;
|
|
@@ -7,7 +7,7 @@ import { renderToString, withServerCustomElementRenderHook } from "@ecopages/jsx
|
|
|
7
7
|
import { ECOPAGES_JSX_PLUGIN_NAME } from "./ecopages-jsx.constants.js";
|
|
8
8
|
class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
9
9
|
name = ECOPAGES_JSX_PLUGIN_NAME;
|
|
10
|
-
static
|
|
10
|
+
static radiantServerRuntimeInstallPromise;
|
|
11
11
|
intrinsicCustomElementAssets;
|
|
12
12
|
collectedAssetFrames = [];
|
|
13
13
|
mdxExtensions;
|
|
@@ -71,9 +71,7 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
71
71
|
return this.mdxExtensions.some((ext) => filePath.endsWith(ext));
|
|
72
72
|
}
|
|
73
73
|
async importPageFile(file, options) {
|
|
74
|
-
|
|
75
|
-
await this.ensureRadiantLightDomShimInstalled();
|
|
76
|
-
}
|
|
74
|
+
await this.ensureRadiantServerRuntimeIfEnabled();
|
|
77
75
|
const module = await super.importPageFile(file, options);
|
|
78
76
|
return this.isMdxFile(file) ? this.normalizeMdxPageModule(file, module) : module;
|
|
79
77
|
}
|
|
@@ -193,46 +191,52 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
|
|
|
193
191
|
return this.htmlTransformer.dedupeProcessedAssets(activeFrame);
|
|
194
192
|
}
|
|
195
193
|
async renderJsx(value) {
|
|
196
|
-
|
|
197
|
-
await this.ensureRadiantLightDomShimInstalled();
|
|
198
|
-
}
|
|
194
|
+
await this.ensureRadiantServerRuntimeIfEnabled();
|
|
199
195
|
const collectedAssets = [];
|
|
200
196
|
const html = withServerCustomElementRenderHook(
|
|
201
197
|
this.createIntrinsicCustomElementRenderHook(collectedAssets),
|
|
202
198
|
() => renderToString(value)
|
|
203
199
|
);
|
|
204
|
-
const dedupedAssets = this.
|
|
205
|
-
const activeFrame = this.collectedAssetFrames[this.collectedAssetFrames.length - 1];
|
|
206
|
-
if (activeFrame) {
|
|
207
|
-
activeFrame.push(...dedupedAssets);
|
|
208
|
-
}
|
|
200
|
+
const dedupedAssets = this.recordCollectedAssets(collectedAssets);
|
|
209
201
|
return {
|
|
210
202
|
assets: dedupedAssets,
|
|
211
203
|
html
|
|
212
204
|
};
|
|
213
205
|
}
|
|
214
206
|
async renderEcoComponent(component, props) {
|
|
215
|
-
|
|
216
|
-
await this.ensureRadiantLightDomShimInstalled();
|
|
217
|
-
}
|
|
207
|
+
await this.ensureRadiantServerRuntimeIfEnabled();
|
|
218
208
|
const collectedAssets = [];
|
|
219
209
|
const rendered = await withServerCustomElementRenderHook(
|
|
220
210
|
this.createIntrinsicCustomElementRenderHook(collectedAssets),
|
|
221
211
|
() => this.invokeComponent(component, props)
|
|
222
212
|
);
|
|
213
|
+
this.recordCollectedAssets(collectedAssets);
|
|
214
|
+
return rendered;
|
|
215
|
+
}
|
|
216
|
+
recordCollectedAssets(collectedAssets) {
|
|
217
|
+
const dedupedAssets = this.htmlTransformer.dedupeProcessedAssets(collectedAssets);
|
|
223
218
|
const activeFrame = this.collectedAssetFrames[this.collectedAssetFrames.length - 1];
|
|
224
219
|
if (activeFrame) {
|
|
225
|
-
activeFrame.push(...
|
|
220
|
+
activeFrame.push(...dedupedAssets);
|
|
226
221
|
}
|
|
227
|
-
return
|
|
222
|
+
return dedupedAssets;
|
|
223
|
+
}
|
|
224
|
+
async ensureRadiantServerRuntimeIfEnabled() {
|
|
225
|
+
if (!this.radiantSsrEnabled) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
await this.ensureRadiantServerRuntimeInstalled();
|
|
228
229
|
}
|
|
229
|
-
async
|
|
230
|
-
if (!EcopagesJsxRenderer.
|
|
231
|
-
EcopagesJsxRenderer.
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
async ensureRadiantServerRuntimeInstalled() {
|
|
231
|
+
if (!EcopagesJsxRenderer.radiantServerRuntimeInstallPromise) {
|
|
232
|
+
EcopagesJsxRenderer.radiantServerRuntimeInstallPromise = Promise.all([
|
|
233
|
+
import("@ecopages/radiant/server/render-component"),
|
|
234
|
+
import("@ecopages/radiant/server/light-dom-shim").then((module) => {
|
|
235
|
+
module.installLightDomShim();
|
|
236
|
+
})
|
|
237
|
+
]).then(() => void 0);
|
|
234
238
|
}
|
|
235
|
-
await EcopagesJsxRenderer.
|
|
239
|
+
await EcopagesJsxRenderer.radiantServerRuntimeInstallPromise;
|
|
236
240
|
}
|
|
237
241
|
isFunctionComponent(component) {
|
|
238
242
|
return typeof component === "function";
|
|
@@ -33,8 +33,13 @@ export type EcopagesJsxPluginOptions = Omit<IntegrationPluginConfig, 'name' | 'e
|
|
|
33
33
|
/** Optional JSX route extensions. Defaults to `.tsx`. */
|
|
34
34
|
extensions?: string[];
|
|
35
35
|
/**
|
|
36
|
-
* Whether to include the
|
|
37
|
-
*
|
|
36
|
+
* Whether to include the Radiant integration contract for JSX apps.
|
|
37
|
+
*
|
|
38
|
+
* When enabled, Ecopages JSX:
|
|
39
|
+
* - imports `@ecopages/radiant/server/render-component` before Radiant SSR
|
|
40
|
+
* - exposes browser-safe Radiant bare specifiers through the runtime import map
|
|
41
|
+
* - injects an explicit client bootstrap that calls `installRadiantHydrator()`
|
|
42
|
+
* before intrinsic custom-element modules load
|
|
38
43
|
*
|
|
39
44
|
* Set to `false` when pages do not use Radiant web components.
|
|
40
45
|
* @default true
|
|
@@ -10,6 +10,7 @@
|
|
|
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";
|
|
13
14
|
export interface JsxRuntimeBundleServiceConfig {
|
|
14
15
|
radiant: boolean;
|
|
15
16
|
rootDir?: string;
|
|
@@ -44,9 +45,12 @@ export declare class JsxRuntimeBundleService {
|
|
|
44
45
|
* script plus one `createBrowserRuntimeScriptAsset` per vendor bundle.
|
|
45
46
|
*/
|
|
46
47
|
getDependencies(): Promise<AssetDefinition[]>;
|
|
48
|
+
private createRadiantHydratorBootstrapAsset;
|
|
49
|
+
private createRadiantHydratorBootstrapSource;
|
|
50
|
+
private getArtifactsDir;
|
|
51
|
+
private getEntryImportPath;
|
|
47
52
|
private getOrCreateSpecifierMap;
|
|
48
53
|
private getOrCreateJsxEntryModulePath;
|
|
49
|
-
private getRadiantBrowserRuntimeSpecifiers;
|
|
50
54
|
private getRadiantBrowserRuntimeModules;
|
|
51
55
|
private resolvePackageExportModulePath;
|
|
52
56
|
private getOrCreateRadiantEntryModulePath;
|
|
@@ -2,6 +2,7 @@ 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,
|
|
5
6
|
buildBrowserRuntimeAssetUrl,
|
|
6
7
|
createBrowserRuntimeScriptAsset,
|
|
7
8
|
AssetFactory
|
|
@@ -10,6 +11,7 @@ const VENDOR_FILE_NAMES = {
|
|
|
10
11
|
jsx: "ecopages-jsx-esm.js",
|
|
11
12
|
radiant: "ecopages-radiant-esm.js"
|
|
12
13
|
};
|
|
14
|
+
const RADIANT_HYDRATOR_BOOTSTRAP_ATTRIBUTE = "data-ecopages-jsx-radiant-hydrator";
|
|
13
15
|
function getNamedExportNamesFromModuleSource(source) {
|
|
14
16
|
const exportNames = /* @__PURE__ */ new Set();
|
|
15
17
|
for (const match of source.matchAll(/export\s*\{([^}]+)\}/g)) {
|
|
@@ -37,6 +39,9 @@ function isBrowserRuntimeRadiantSpecifier(exportKey) {
|
|
|
37
39
|
if (exportKey === "." || exportKey.startsWith("./context/")) {
|
|
38
40
|
return true;
|
|
39
41
|
}
|
|
42
|
+
if (exportKey === "./client/hydrator") {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
40
45
|
if (exportKey.startsWith("./decorators/") || exportKey.startsWith("./helpers/")) {
|
|
41
46
|
return true;
|
|
42
47
|
}
|
|
@@ -105,13 +110,18 @@ class JsxRuntimeBundleService {
|
|
|
105
110
|
bundle: false,
|
|
106
111
|
content: JSON.stringify({ imports: specifierMap }, null, 2),
|
|
107
112
|
attributes: { type: "importmap" }
|
|
108
|
-
})
|
|
113
|
+
})
|
|
114
|
+
];
|
|
115
|
+
if (this.config.radiant) {
|
|
116
|
+
deps.push(this.createRadiantHydratorBootstrapAsset());
|
|
117
|
+
}
|
|
118
|
+
deps.push(
|
|
109
119
|
createBrowserRuntimeScriptAsset({
|
|
110
120
|
importPath: jsxEntryModulePath,
|
|
111
121
|
name: "ecopages-jsx-esm",
|
|
112
122
|
fileName: VENDOR_FILE_NAMES.jsx
|
|
113
123
|
})
|
|
114
|
-
|
|
124
|
+
);
|
|
115
125
|
if (this.config.radiant) {
|
|
116
126
|
const radiantEntryModulePath = await this.getOrCreateRadiantEntryModulePath();
|
|
117
127
|
deps.push(
|
|
@@ -124,6 +134,31 @@ class JsxRuntimeBundleService {
|
|
|
124
134
|
}
|
|
125
135
|
return deps;
|
|
126
136
|
}
|
|
137
|
+
createRadiantHydratorBootstrapAsset() {
|
|
138
|
+
return AssetFactory.createInlineContentScript({
|
|
139
|
+
position: "head",
|
|
140
|
+
bundle: false,
|
|
141
|
+
content: this.createRadiantHydratorBootstrapSource(),
|
|
142
|
+
attributes: {
|
|
143
|
+
...BROWSER_RUNTIME_SCRIPT_ATTRIBUTES,
|
|
144
|
+
[RADIANT_HYDRATOR_BOOTSTRAP_ATTRIBUTE]: "true"
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
createRadiantHydratorBootstrapSource() {
|
|
149
|
+
return [
|
|
150
|
+
"import { installRadiantHydrator } from '@ecopages/radiant/client/hydrator';",
|
|
151
|
+
"installRadiantHydrator();"
|
|
152
|
+
].join("\n");
|
|
153
|
+
}
|
|
154
|
+
getArtifactsDir() {
|
|
155
|
+
const rootDir = this.config.rootDir ?? process.cwd();
|
|
156
|
+
return path.join(rootDir, "node_modules", ".cache", "ecopages-browser-runtime");
|
|
157
|
+
}
|
|
158
|
+
getEntryImportPath(fromDir, targetPath) {
|
|
159
|
+
const relativeModulePath = path.relative(fromDir, targetPath).split(path.sep).join("/");
|
|
160
|
+
return relativeModulePath.startsWith(".") ? relativeModulePath : `./${relativeModulePath}`;
|
|
161
|
+
}
|
|
127
162
|
getOrCreateSpecifierMap() {
|
|
128
163
|
if (this.cachedSpecifierMap) {
|
|
129
164
|
return this.cachedSpecifierMap;
|
|
@@ -155,8 +190,7 @@ class JsxRuntimeBundleService {
|
|
|
155
190
|
if (this.cachedJsxEntryModulePath) {
|
|
156
191
|
return this.cachedJsxEntryModulePath;
|
|
157
192
|
}
|
|
158
|
-
const
|
|
159
|
-
const artifactsDir = path.join(rootDir, "node_modules", ".cache", "ecopages-browser-runtime");
|
|
193
|
+
const artifactsDir = this.getArtifactsDir();
|
|
160
194
|
const filePath = path.join(artifactsDir, "ecopages-jsx-esm-entry.mjs");
|
|
161
195
|
const manifestPath = findPackageManifestPath("@ecopages/jsx");
|
|
162
196
|
const packageDir = path.dirname(realpathSync(manifestPath));
|
|
@@ -166,16 +200,12 @@ class JsxRuntimeBundleService {
|
|
|
166
200
|
".",
|
|
167
201
|
jsxPkg.exports?.["."]
|
|
168
202
|
);
|
|
169
|
-
const
|
|
170
|
-
const entryImportPath = relativeModulePath.startsWith(".") ? relativeModulePath : `./${relativeModulePath}`;
|
|
203
|
+
const entryImportPath = this.getEntryImportPath(artifactsDir, jsxModulePath);
|
|
171
204
|
mkdirSync(artifactsDir, { recursive: true });
|
|
172
205
|
writeFileSync(filePath, [`export * from '${entryImportPath}';`].join("\n"), "utf8");
|
|
173
206
|
this.cachedJsxEntryModulePath = filePath;
|
|
174
207
|
return filePath;
|
|
175
208
|
}
|
|
176
|
-
getRadiantBrowserRuntimeSpecifiers() {
|
|
177
|
-
return this.getRadiantBrowserRuntimeModules().map(({ exportKey }) => `@ecopages/radiant${exportKey.slice(1)}`);
|
|
178
|
-
}
|
|
179
209
|
getRadiantBrowserRuntimeModules() {
|
|
180
210
|
const manifestPath = findPackageManifestPath("@ecopages/radiant");
|
|
181
211
|
const packageDir = path.dirname(realpathSync(manifestPath));
|
|
@@ -205,8 +235,7 @@ class JsxRuntimeBundleService {
|
|
|
205
235
|
if (this.cachedRadiantEntryModulePath) {
|
|
206
236
|
return this.cachedRadiantEntryModulePath;
|
|
207
237
|
}
|
|
208
|
-
const
|
|
209
|
-
const artifactsDir = path.join(rootDir, "node_modules", ".cache", "ecopages-browser-runtime");
|
|
238
|
+
const artifactsDir = this.getArtifactsDir();
|
|
210
239
|
const filePath = path.join(artifactsDir, "ecopages-radiant-esm-entry.mjs");
|
|
211
240
|
const seenExports = /* @__PURE__ */ new Set();
|
|
212
241
|
const statements = [];
|
|
@@ -216,8 +245,7 @@ class JsxRuntimeBundleService {
|
|
|
216
245
|
if (exportNames.length === 0) {
|
|
217
246
|
continue;
|
|
218
247
|
}
|
|
219
|
-
const
|
|
220
|
-
const entryImportPath = relativeModulePath.startsWith(".") ? relativeModulePath : `./${relativeModulePath}`;
|
|
248
|
+
const entryImportPath = this.getEntryImportPath(artifactsDir, module.modulePath);
|
|
221
249
|
statements.push(`export { ${exportNames.join(", ")} } from '${entryImportPath}';`);
|
|
222
250
|
for (const exportName of exportNames) {
|
|
223
251
|
seenExports.add(exportName);
|
|
@@ -229,5 +257,6 @@ class JsxRuntimeBundleService {
|
|
|
229
257
|
}
|
|
230
258
|
}
|
|
231
259
|
export {
|
|
232
|
-
JsxRuntimeBundleService
|
|
260
|
+
JsxRuntimeBundleService,
|
|
261
|
+
RADIANT_HYDRATOR_BOOTSTRAP_ATTRIBUTE
|
|
233
262
|
};
|