@ecopages/ecopages-jsx 0.2.0-alpha.21 → 0.2.0-alpha.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,8 +8,10 @@
8
8
 
9
9
  ### Bug Fixes
10
10
 
11
+ - Patched the Ecopages JSX browser runtime bundle so nested SVG templates restore canonical camel-cased SVG tag names during client rendering.
11
12
  - Aligned Ecopages JSX peer dependency ranges with the current `@ecopages/jsx` and `@ecopages/radiant` alpha releases.
12
13
  - 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.
14
+ - 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`.
13
15
  - Fixed Radiant SSR page inspection to install the light-DOM shim before JSX page modules are imported outside the normal render pass.
14
16
  - Restored direct `EcopagesJsxPlugin` construction so the exported class still accepts the public plugin options shape.
15
17
  - Fixed intrinsic custom-element asset discovery so Ecopages JSX registers scripts declared with decorator and function-call `customElement(...)` syntax.
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 calls `installRadiantHydrator()` from `@ecopages/radiant/client/hydrator` before intrinsic custom-element modules load.
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 `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.
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 relying on unrelated imports to install the Radiant hydrator as a side effect.
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.21",
3
+ "version": "0.2.0-alpha.23",
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.21",
25
- "@ecopages/jsx": "^0.3.0-alpha.2",
26
- "@ecopages/radiant": "^0.3.0-alpha.2"
24
+ "@ecopages/core": "0.2.0-alpha.23",
25
+ "@ecopages/jsx": "0.3.0-alpha.5",
26
+ "@ecopages/radiant": "0.3.0-alpha.5"
27
27
  }
28
28
  }
@@ -38,8 +38,9 @@ export type EcopagesJsxPluginOptions = Omit<IntegrationPluginConfig, 'name' | 'e
38
38
  * When enabled, Ecopages JSX:
39
39
  * - imports `@ecopages/radiant/server/render-component` before Radiant SSR
40
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
41
+ * - injects an explicit client bootstrap that imports
42
+ * `@ecopages/radiant/client/install-hydrator` before intrinsic
43
+ * custom-element modules load
43
44
  *
44
45
  * Set to `false` when pages do not use Radiant web components.
45
46
  * @default true
@@ -20,6 +20,7 @@ export declare class JsxRuntimeBundleService {
20
20
  private cachedSpecifierMap;
21
21
  private cachedJsxEntryModulePath;
22
22
  private cachedRadiantEntryModulePath;
23
+ private cachedRadiantInstallHydratorEntryModulePath;
23
24
  constructor(config: JsxRuntimeBundleServiceConfig);
24
25
  setRootDir(rootDir: string | undefined): void;
25
26
  /**
@@ -54,4 +55,5 @@ export declare class JsxRuntimeBundleService {
54
55
  private getRadiantBrowserRuntimeModules;
55
56
  private resolvePackageExportModulePath;
56
57
  private getOrCreateRadiantEntryModulePath;
58
+ private getOrCreateRadiantInstallHydratorEntryModulePath;
57
59
  }
@@ -9,9 +9,21 @@ import {
9
9
  } from "@ecopages/core/services/asset-processing-service";
10
10
  const VENDOR_FILE_NAMES = {
11
11
  jsx: "ecopages-jsx-esm.js",
12
- radiant: "ecopages-radiant-esm.js"
12
+ radiant: "ecopages-radiant-esm.js",
13
+ radiantInstallHydrator: "ecopages-radiant-install-hydrator-esm.js"
13
14
  };
14
15
  const RADIANT_HYDRATOR_BOOTSTRAP_ATTRIBUTE = "data-ecopages-jsx-radiant-hydrator";
16
+ 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}";
17
+ const JSX_RUNTIME_NAMESPACE_REPAIR_PATCH = [
18
+ "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'};",
19
+ "function eopGetCanonicalSvgLocalName(W){return eopCanonicalSvgLocalNames[W]??W}",
20
+ "function eopIsSvgNamespace(W){return W===eopSvgNamespace}",
21
+ "function rG(W,G,J){let j=G instanceof Element?G:G?.parentElement,U=j?.namespaceURI??K9,$=j?.localName;eopRepairNamespaceFragment(W,U??eopHtmlNamespace,$,J)}",
22
+ "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)}",
23
+ "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)}",
24
+ "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,$)}}",
25
+ "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}"
26
+ ].join("");
15
27
  function getNamedExportNamesFromModuleSource(source) {
16
28
  const exportNames = /* @__PURE__ */ new Set();
17
29
  for (const match of source.matchAll(/export\s*\{([^}]+)\}/g)) {
@@ -39,13 +51,33 @@ function isBrowserRuntimeRadiantSpecifier(exportKey) {
39
51
  if (exportKey === "." || exportKey.startsWith("./context/")) {
40
52
  return true;
41
53
  }
54
+ if (exportKey === "./controller-registry") {
55
+ return true;
56
+ }
42
57
  if (exportKey === "./client/hydrator") {
43
58
  return true;
44
59
  }
60
+ if (exportKey === "./client/install-hydrator") {
61
+ return true;
62
+ }
45
63
  if (exportKey.startsWith("./decorators/") || exportKey.startsWith("./helpers/")) {
46
64
  return true;
47
65
  }
48
- return exportKey === "./core/radiant-component" || exportKey === "./core/radiant-element";
66
+ return exportKey === "./core/radiant-controller" || exportKey === "./core/radiant-element";
67
+ }
68
+ function replaceExactOnce(source, search, replacement, label) {
69
+ if (!source.includes(search)) {
70
+ throw new Error(`Could not find ${label} in @ecopages/jsx browser runtime source`);
71
+ }
72
+ return source.replace(search, replacement);
73
+ }
74
+ function createPatchedJsxBrowserRuntimeSource(source) {
75
+ return replaceExactOnce(
76
+ source,
77
+ JSX_RUNTIME_NAMESPACE_REPAIR_SNIPPET,
78
+ JSX_RUNTIME_NAMESPACE_REPAIR_PATCH,
79
+ "SVG namespace repair snippet"
80
+ );
49
81
  }
50
82
  function findPackageManifestPath(packageName) {
51
83
  let currentDir = path.dirname(new URL(import.meta.url).pathname);
@@ -67,6 +99,7 @@ class JsxRuntimeBundleService {
67
99
  cachedSpecifierMap;
68
100
  cachedJsxEntryModulePath;
69
101
  cachedRadiantEntryModulePath;
102
+ cachedRadiantInstallHydratorEntryModulePath;
70
103
  constructor(config) {
71
104
  this.config = config;
72
105
  }
@@ -124,11 +157,17 @@ class JsxRuntimeBundleService {
124
157
  );
125
158
  if (this.config.radiant) {
126
159
  const radiantEntryModulePath = await this.getOrCreateRadiantEntryModulePath();
160
+ const radiantInstallHydratorEntryModulePath = await this.getOrCreateRadiantInstallHydratorEntryModulePath();
127
161
  deps.push(
128
162
  createBrowserRuntimeScriptAsset({
129
163
  importPath: radiantEntryModulePath,
130
164
  name: "ecopages-radiant-esm",
131
165
  fileName: VENDOR_FILE_NAMES.radiant
166
+ }),
167
+ createBrowserRuntimeScriptAsset({
168
+ importPath: radiantInstallHydratorEntryModulePath,
169
+ name: "ecopages-radiant-install-hydrator-esm",
170
+ fileName: VENDOR_FILE_NAMES.radiantInstallHydrator
132
171
  })
133
172
  );
134
173
  }
@@ -146,10 +185,7 @@ class JsxRuntimeBundleService {
146
185
  });
147
186
  }
148
187
  createRadiantHydratorBootstrapSource() {
149
- return [
150
- "import { installRadiantHydrator } from '@ecopages/radiant/client/hydrator';",
151
- "installRadiantHydrator();"
152
- ].join("\n");
188
+ return "import '@ecopages/radiant/client/install-hydrator';";
153
189
  }
154
190
  getArtifactsDir() {
155
191
  const rootDir = this.config.rootDir ?? process.cwd();
@@ -172,6 +208,9 @@ class JsxRuntimeBundleService {
172
208
  };
173
209
  if (this.config.radiant) {
174
210
  const radiantVendorUrl = buildBrowserRuntimeAssetUrl(VENDOR_FILE_NAMES.radiant);
211
+ const radiantInstallHydratorVendorUrl = buildBrowserRuntimeAssetUrl(
212
+ VENDOR_FILE_NAMES.radiantInstallHydrator
213
+ );
175
214
  const radiantPkg = JSON.parse(
176
215
  readFileSync(findPackageManifestPath("@ecopages/radiant"), "utf8")
177
216
  );
@@ -180,7 +219,7 @@ class JsxRuntimeBundleService {
180
219
  continue;
181
220
  }
182
221
  const specifier = key === "." ? "@ecopages/radiant" : `@ecopages/radiant${key.slice(1)}`;
183
- specifierMap[specifier] = radiantVendorUrl;
222
+ specifierMap[specifier] = key === "./client/install-hydrator" ? radiantInstallHydratorVendorUrl : radiantVendorUrl;
184
223
  }
185
224
  }
186
225
  this.cachedSpecifierMap = specifierMap;
@@ -200,9 +239,9 @@ class JsxRuntimeBundleService {
200
239
  ".",
201
240
  jsxPkg.exports?.["."]
202
241
  );
203
- const entryImportPath = this.getEntryImportPath(artifactsDir, jsxModulePath);
242
+ const patchedRuntimeSource = createPatchedJsxBrowserRuntimeSource(readFileSync(jsxModulePath, "utf8"));
204
243
  mkdirSync(artifactsDir, { recursive: true });
205
- writeFileSync(filePath, [`export * from '${entryImportPath}';`].join("\n"), "utf8");
244
+ writeFileSync(filePath, patchedRuntimeSource, "utf8");
206
245
  this.cachedJsxEntryModulePath = filePath;
207
246
  return filePath;
208
247
  }
@@ -255,6 +294,31 @@ class JsxRuntimeBundleService {
255
294
  this.cachedRadiantEntryModulePath = filePath;
256
295
  return filePath;
257
296
  }
297
+ async getOrCreateRadiantInstallHydratorEntryModulePath() {
298
+ if (this.cachedRadiantInstallHydratorEntryModulePath) {
299
+ return this.cachedRadiantInstallHydratorEntryModulePath;
300
+ }
301
+ const artifactsDir = this.getArtifactsDir();
302
+ const filePath = path.join(artifactsDir, "ecopages-radiant-install-hydrator-esm-entry.mjs");
303
+ const manifestPath = findPackageManifestPath("@ecopages/radiant");
304
+ const packageDir = path.dirname(realpathSync(manifestPath));
305
+ const radiantPkg = JSON.parse(readFileSync(manifestPath, "utf8"));
306
+ const modulePath = this.resolvePackageExportModulePath(
307
+ packageDir,
308
+ "./client/install-hydrator",
309
+ radiantPkg.exports?.["./client/install-hydrator"]
310
+ );
311
+ mkdirSync(artifactsDir, { recursive: true });
312
+ writeFileSync(
313
+ filePath,
314
+ `import '${this.getEntryImportPath(artifactsDir, modulePath)}';
315
+ export {};
316
+ `,
317
+ "utf8"
318
+ );
319
+ this.cachedRadiantInstallHydratorEntryModulePath = filePath;
320
+ return filePath;
321
+ }
258
322
  }
259
323
  export {
260
324
  JsxRuntimeBundleService,