@ecopages/ecopages-jsx 0.2.1 → 0.3.1

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.
Files changed (31) hide show
  1. package/README.md +69 -23
  2. package/package.json +25 -6
  3. package/src/dev-transform/ecopages-jsx-dev-transform-contributor.d.ts +17 -0
  4. package/src/dev-transform/ecopages-jsx-dev-transform-contributor.js +21 -0
  5. package/src/eco-embed.d.ts +11 -0
  6. package/src/eco-embed.js +9 -0
  7. package/src/ecopages-jsx-hmr-ownership.d.ts +43 -0
  8. package/src/ecopages-jsx-hmr-ownership.js +91 -0
  9. package/src/ecopages-jsx-hmr-strategy.d.ts +19 -0
  10. package/src/ecopages-jsx-hmr-strategy.js +67 -0
  11. package/src/ecopages-jsx-mdx.d.ts +32 -0
  12. package/src/ecopages-jsx-mdx.js +48 -0
  13. package/src/ecopages-jsx-radiant-ssr-policy.d.ts +39 -0
  14. package/src/ecopages-jsx-radiant-ssr-policy.js +122 -0
  15. package/src/ecopages-jsx-renderer.d.ts +10 -52
  16. package/src/ecopages-jsx-renderer.js +143 -251
  17. package/src/ecopages-jsx.constants.d.ts +7 -0
  18. package/src/ecopages-jsx.constants.js +7 -0
  19. package/src/ecopages-jsx.plugin.d.ts +21 -93
  20. package/src/ecopages-jsx.plugin.js +135 -237
  21. package/src/ecopages-jsx.types.d.ts +32 -0
  22. package/src/ecopages-jsx.types.js +1 -0
  23. package/src/page-owned-content-script-grouping.d.ts +11 -0
  24. package/src/page-owned-content-script-grouping.js +57 -0
  25. package/src/radiant-registered-script-ssr-invalidation.d.ts +19 -0
  26. package/src/radiant-registered-script-ssr-invalidation.js +49 -0
  27. package/src/resolve-radiant-install-hydrator.d.ts +2 -0
  28. package/src/resolve-radiant-install-hydrator.js +3 -0
  29. package/CHANGELOG.md +0 -15
  30. package/src/services/jsx-runtime-bundle.service.d.ts +0 -51
  31. package/src/services/jsx-runtime-bundle.service.js +0 -206
@@ -0,0 +1,49 @@
1
+ import { fileSystem } from '@ecopages/file-system';
2
+ import { CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES, invalidateCustomElementScriptPreload, } from '@ecopages/core/integration';
3
+ const CUSTOM_ELEMENT_TAG_PATTERN = /@customElement\s*\(\s*['"`]([^'"`]+)['"`]/u;
4
+ /**
5
+ * Reads the custom-element tag name from a registered script module source file.
6
+ */
7
+ export function resolveRadiantCustomElementTag(scriptPath) {
8
+ if (!fileSystem.exists(scriptPath)) {
9
+ return undefined;
10
+ }
11
+ const content = fileSystem.readFileSync(scriptPath);
12
+ const match = content.match(CUSTOM_ELEMENT_TAG_PATTERN);
13
+ return match?.[1];
14
+ }
15
+ /**
16
+ * Removes one custom-element definition from the active runtime registry.
17
+ *
18
+ * @remarks
19
+ * Radiant's `@customElement` decorator and the SSR light-DOM shim both skip
20
+ * re-registration when a tag is already defined. During dev, server requests
21
+ * reuse the same process-global registry, so script edits would otherwise keep
22
+ * serving stale SSR markup until a full dev-server restart.
23
+ */
24
+ export function clearRadiantCustomElementDefinition(tagName) {
25
+ const registry = globalThis.customElements;
26
+ if (!registry) {
27
+ return;
28
+ }
29
+ if (typeof registry.delete === 'function') {
30
+ registry.delete(tagName);
31
+ return;
32
+ }
33
+ const definitions = registry.definitions;
34
+ if (definitions instanceof Map && definitions.has(tagName)) {
35
+ definitions.delete(tagName);
36
+ }
37
+ }
38
+ /**
39
+ * Clears the SSR custom-element registry entry for one registered Radiant script module.
40
+ */
41
+ export function invalidateRadiantRegisteredScriptSsrRegistration(preloadScopes, scriptPath) {
42
+ invalidateCustomElementScriptPreload(preloadScopes, scriptPath, CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES.ecopagesJsx);
43
+ const tagName = resolveRadiantCustomElementTag(scriptPath);
44
+ if (!tagName) {
45
+ return false;
46
+ }
47
+ clearRadiantCustomElementDefinition(tagName);
48
+ return true;
49
+ }
@@ -0,0 +1,2 @@
1
+ /** Absolute path to `@ecopages/radiant/client/install-hydrator` from this integration package. */
2
+ export declare const RADIANT_INSTALL_HYDRATOR_FILEPATH: string;
@@ -0,0 +1,3 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ /** Absolute path to `@ecopages/radiant/client/install-hydrator` from this integration package. */
3
+ export const RADIANT_INSTALL_HYDRATOR_FILEPATH = fileURLToPath(import.meta.resolve('@ecopages/radiant/client/install-hydrator'));
package/CHANGELOG.md DELETED
@@ -1,15 +0,0 @@
1
- # Changelog
2
-
3
- ## [UNRELEASED] -- TBD
4
-
5
- ### Features
6
-
7
- - Added the Ecopages JSX integration with optional Radiant runtime support and optional MDX routes compiled against `@ecopages/jsx`.
8
-
9
- ### Bug Fixes
10
-
11
- - Fixed mixed-renderer JSX boundary resolution, Radiant/browser runtime asset mapping, required peer dependency wiring, Bun-only MDX loader setup, and multi-dot MDX extension matching.
12
-
13
- ### Documentation
14
-
15
- - Updated the README to document JSX route extensions, Radiant defaults, MDX configuration, and mixed-renderer boundary ownership.
@@ -1,51 +0,0 @@
1
- /**
2
- * Runtime bundle service for the JSX integration.
3
- *
4
- * Owns creation of browser runtime vendor assets, the import map specifier
5
- * mapping, and the build external plugin. Radiant sub-path specifiers are
6
- * derived at runtime from `@ecopages/radiant/package.json` exports so the
7
- * list stays in sync with whatever version is installed.
8
- *
9
- * @module
10
- */
11
- import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
12
- import { type AssetDefinition } from '@ecopages/core/services/asset-processing-service';
13
- export interface JsxRuntimeBundleServiceConfig {
14
- radiant: boolean;
15
- rootDir?: string;
16
- }
17
- export declare class JsxRuntimeBundleService {
18
- private readonly config;
19
- private cachedSpecifierMap;
20
- private cachedRadiantEntryModulePath;
21
- constructor(config: JsxRuntimeBundleServiceConfig);
22
- setRootDir(rootDir: string | undefined): void;
23
- /**
24
- * Returns the build plugin that aliases JSX and Radiant runtime specifiers to
25
- * their emitted browser vendor URLs.
26
- *
27
- * @remarks
28
- * The returned plugin both externalizes the mapped specifiers during bundle
29
- * resolution and exposes alias metadata so Ecopages can rewrite any emitted JS
30
- * imports that still reference bare runtime specifiers.
31
- */
32
- getBuildPlugin(): EcoBuildPlugin;
33
- /**
34
- * Builds the bare-specifier-to-vendor-URL map for the browser import map.
35
- *
36
- * JSX sub-paths are always included. When `radiant` is enabled, radiant
37
- * sub-paths are derived from `@ecopages/radiant/package.json` exports and
38
- * the result is cached for the lifetime of this service instance.
39
- */
40
- getSpecifierMap(): Promise<Record<string, string>>;
41
- /**
42
- * Builds the full list of vendor asset definitions: the import map inline
43
- * script plus one `createBrowserRuntimeScriptAsset` per vendor bundle.
44
- */
45
- getDependencies(): Promise<AssetDefinition[]>;
46
- private getOrCreateSpecifierMap;
47
- private getRadiantBrowserRuntimeSpecifiers;
48
- private getRadiantBrowserRuntimeModules;
49
- private resolveRadiantExportModulePath;
50
- private getOrCreateRadiantEntryModulePath;
51
- }
@@ -1,206 +0,0 @@
1
- import path from "node:path";
2
- import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
3
- import { createRuntimeSpecifierAliasPlugin } from "@ecopages/core/build/runtime-specifier-alias-plugin";
4
- import {
5
- buildBrowserRuntimeAssetUrl,
6
- createBrowserRuntimeScriptAsset,
7
- AssetFactory
8
- } from "@ecopages/core/services/asset-processing-service";
9
- const VENDOR_FILE_NAMES = {
10
- jsx: "ecopages-jsx-esm.js",
11
- radiant: "ecopages-radiant-esm.js"
12
- };
13
- function getNamedExportNamesFromModuleSource(source) {
14
- const exportNames = /* @__PURE__ */ new Set();
15
- for (const match of source.matchAll(/export\s*\{([^}]+)\}/g)) {
16
- for (const specifier of match[1].split(",")) {
17
- const trimmedSpecifier = specifier.trim();
18
- if (!trimmedSpecifier) {
19
- continue;
20
- }
21
- const aliasMatch = trimmedSpecifier.match(/(?:.+\s+as\s+)?([A-Z_a-z$][\w$]*)$/);
22
- if (aliasMatch?.[1] && aliasMatch[1] !== "default") {
23
- exportNames.add(aliasMatch[1]);
24
- }
25
- }
26
- }
27
- for (const match of source.matchAll(
28
- /export\s+(?:async\s+)?(?:const|function|class|let|var)\s+([A-Z_a-z$][\w$]*)/g
29
- )) {
30
- if (match[1] !== "default") {
31
- exportNames.add(match[1]);
32
- }
33
- }
34
- return [...exportNames].sort();
35
- }
36
- function isBrowserRuntimeRadiantSpecifier(exportKey) {
37
- if (exportKey === "." || exportKey.startsWith("./context/")) {
38
- return true;
39
- }
40
- if (exportKey.startsWith("./decorators/") || exportKey.startsWith("./helpers/")) {
41
- return true;
42
- }
43
- return exportKey === "./core/radiant-component" || exportKey === "./core/radiant-element";
44
- }
45
- function findPackageManifestPath(packageName) {
46
- let currentDir = path.dirname(new URL(import.meta.url).pathname);
47
- while (true) {
48
- const candidatePath = path.join(currentDir, "node_modules", packageName, "package.json");
49
- if (existsSync(candidatePath)) {
50
- return candidatePath;
51
- }
52
- const parentDir = path.dirname(currentDir);
53
- if (parentDir === currentDir) {
54
- break;
55
- }
56
- currentDir = parentDir;
57
- }
58
- throw new Error(`Could not locate ${packageName}/package.json from ${import.meta.url}`);
59
- }
60
- class JsxRuntimeBundleService {
61
- config;
62
- cachedSpecifierMap;
63
- cachedRadiantEntryModulePath;
64
- constructor(config) {
65
- this.config = config;
66
- }
67
- setRootDir(rootDir) {
68
- this.config.rootDir = rootDir;
69
- }
70
- /**
71
- * Returns the build plugin that aliases JSX and Radiant runtime specifiers to
72
- * their emitted browser vendor URLs.
73
- *
74
- * @remarks
75
- * The returned plugin both externalizes the mapped specifiers during bundle
76
- * resolution and exposes alias metadata so Ecopages can rewrite any emitted JS
77
- * imports that still reference bare runtime specifiers.
78
- */
79
- getBuildPlugin() {
80
- return createRuntimeSpecifierAliasPlugin(this.getOrCreateSpecifierMap(), {
81
- name: "ecopages-jsx-runtime-alias"
82
- });
83
- }
84
- /**
85
- * Builds the bare-specifier-to-vendor-URL map for the browser import map.
86
- *
87
- * JSX sub-paths are always included. When `radiant` is enabled, radiant
88
- * sub-paths are derived from `@ecopages/radiant/package.json` exports and
89
- * the result is cached for the lifetime of this service instance.
90
- */
91
- async getSpecifierMap() {
92
- return this.getOrCreateSpecifierMap();
93
- }
94
- /**
95
- * Builds the full list of vendor asset definitions: the import map inline
96
- * script plus one `createBrowserRuntimeScriptAsset` per vendor bundle.
97
- */
98
- async getDependencies() {
99
- const specifierMap = await this.getSpecifierMap();
100
- const deps = [
101
- AssetFactory.createInlineContentScript({
102
- position: "head",
103
- bundle: false,
104
- content: JSON.stringify({ imports: specifierMap }, null, 2),
105
- attributes: { type: "importmap" }
106
- }),
107
- createBrowserRuntimeScriptAsset({
108
- importPath: "@ecopages/jsx",
109
- name: "ecopages-jsx-esm",
110
- fileName: VENDOR_FILE_NAMES.jsx
111
- })
112
- ];
113
- if (this.config.radiant) {
114
- const radiantEntryModulePath = await this.getOrCreateRadiantEntryModulePath();
115
- deps.push(
116
- createBrowserRuntimeScriptAsset({
117
- importPath: radiantEntryModulePath,
118
- name: "ecopages-radiant-esm",
119
- fileName: VENDOR_FILE_NAMES.radiant
120
- })
121
- );
122
- }
123
- return deps;
124
- }
125
- getOrCreateSpecifierMap() {
126
- if (this.cachedSpecifierMap) {
127
- return this.cachedSpecifierMap;
128
- }
129
- const jsxVendorUrl = buildBrowserRuntimeAssetUrl(VENDOR_FILE_NAMES.jsx);
130
- const specifierMap = {
131
- "@ecopages/jsx": jsxVendorUrl,
132
- "@ecopages/jsx/server": jsxVendorUrl,
133
- "@ecopages/jsx/client": jsxVendorUrl,
134
- "@ecopages/jsx/jsx-runtime": jsxVendorUrl,
135
- "@ecopages/jsx/jsx-dev-runtime": jsxVendorUrl
136
- };
137
- if (this.config.radiant) {
138
- const radiantVendorUrl = buildBrowserRuntimeAssetUrl(VENDOR_FILE_NAMES.radiant);
139
- const radiantPkg = JSON.parse(
140
- readFileSync(findPackageManifestPath("@ecopages/radiant"), "utf8")
141
- );
142
- for (const key of Object.keys(radiantPkg.exports ?? {})) {
143
- if (!isBrowserRuntimeRadiantSpecifier(key)) {
144
- continue;
145
- }
146
- const specifier = key === "." ? "@ecopages/radiant" : `@ecopages/radiant${key.slice(1)}`;
147
- specifierMap[specifier] = radiantVendorUrl;
148
- }
149
- }
150
- this.cachedSpecifierMap = specifierMap;
151
- return specifierMap;
152
- }
153
- getRadiantBrowserRuntimeSpecifiers() {
154
- return this.getRadiantBrowserRuntimeModules().map(({ exportKey }) => `@ecopages/radiant${exportKey.slice(1)}`);
155
- }
156
- getRadiantBrowserRuntimeModules() {
157
- const manifestPath = findPackageManifestPath("@ecopages/radiant");
158
- const packageDir = path.dirname(realpathSync(manifestPath));
159
- const radiantPkg = JSON.parse(readFileSync(manifestPath, "utf8"));
160
- return Object.entries(radiantPkg.exports ?? {}).filter(([key]) => isBrowserRuntimeRadiantSpecifier(key) && key !== ".").sort(([left], [right]) => left.localeCompare(right)).map(([exportKey, exportTarget]) => ({
161
- exportKey,
162
- modulePath: this.resolveRadiantExportModulePath(packageDir, exportKey, exportTarget)
163
- })).filter((module) => existsSync(module.modulePath));
164
- }
165
- resolveRadiantExportModulePath(packageDir, exportKey, exportTarget) {
166
- if (typeof exportTarget === "string") {
167
- return path.resolve(packageDir, exportTarget);
168
- }
169
- if (exportTarget && typeof exportTarget === "object" && "import" in exportTarget) {
170
- const importTarget = exportTarget.import;
171
- if (typeof importTarget === "string") {
172
- return path.resolve(packageDir, importTarget);
173
- }
174
- }
175
- throw new Error(`Missing import target for @ecopages/radiant export ${exportKey}`);
176
- }
177
- async getOrCreateRadiantEntryModulePath() {
178
- if (this.cachedRadiantEntryModulePath) {
179
- return this.cachedRadiantEntryModulePath;
180
- }
181
- const rootDir = this.config.rootDir ?? process.cwd();
182
- const artifactsDir = path.join(rootDir, "node_modules", ".cache", "ecopages-browser-runtime");
183
- const filePath = path.join(artifactsDir, "ecopages-radiant-esm-entry.mjs");
184
- const seenExports = /* @__PURE__ */ new Set();
185
- const statements = [];
186
- mkdirSync(artifactsDir, { recursive: true });
187
- for (const module of this.getRadiantBrowserRuntimeModules()) {
188
- const exportNames = getNamedExportNamesFromModuleSource(readFileSync(module.modulePath, "utf8")).filter((name) => !seenExports.has(name)).filter((name) => /^[$A-Z_a-z][$\w]*$/.test(name)).sort();
189
- if (exportNames.length === 0) {
190
- continue;
191
- }
192
- const relativeModulePath = path.relative(artifactsDir, module.modulePath).split(path.sep).join("/");
193
- const entryImportPath = relativeModulePath.startsWith(".") ? relativeModulePath : `./${relativeModulePath}`;
194
- statements.push(`export { ${exportNames.join(", ")} } from '${entryImportPath}';`);
195
- for (const exportName of exportNames) {
196
- seenExports.add(exportName);
197
- }
198
- }
199
- writeFileSync(filePath, statements.join("\n"), "utf8");
200
- this.cachedRadiantEntryModulePath = filePath;
201
- return filePath;
202
- }
203
- }
204
- export {
205
- JsxRuntimeBundleService
206
- };