@ecopages/ecopages-jsx 0.2.1 → 0.3.0
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/README.md +69 -23
- package/package.json +24 -6
- package/src/dev-transform/ecopages-jsx-dev-transform-contributor.d.ts +17 -0
- package/src/dev-transform/ecopages-jsx-dev-transform-contributor.js +21 -0
- package/src/eco-embed.d.ts +11 -0
- package/src/eco-embed.js +9 -0
- package/src/ecopages-jsx-hmr-ownership.d.ts +43 -0
- package/src/ecopages-jsx-hmr-ownership.js +91 -0
- package/src/ecopages-jsx-hmr-strategy.d.ts +19 -0
- package/src/ecopages-jsx-hmr-strategy.js +67 -0
- package/src/ecopages-jsx-mdx.d.ts +32 -0
- package/src/ecopages-jsx-mdx.js +48 -0
- package/src/ecopages-jsx-radiant-ssr-policy.d.ts +39 -0
- package/src/ecopages-jsx-radiant-ssr-policy.js +122 -0
- package/src/ecopages-jsx-renderer.d.ts +10 -52
- package/src/ecopages-jsx-renderer.js +143 -251
- package/src/ecopages-jsx.constants.d.ts +7 -0
- package/src/ecopages-jsx.constants.js +7 -0
- package/src/ecopages-jsx.plugin.d.ts +21 -93
- package/src/ecopages-jsx.plugin.js +135 -237
- package/src/ecopages-jsx.types.d.ts +32 -0
- package/src/ecopages-jsx.types.js +1 -0
- package/src/page-owned-content-script-grouping.d.ts +11 -0
- package/src/page-owned-content-script-grouping.js +57 -0
- package/src/radiant-registered-script-ssr-invalidation.d.ts +19 -0
- package/src/radiant-registered-script-ssr-invalidation.js +49 -0
- package/src/resolve-radiant-install-hydrator.d.ts +2 -0
- package/src/resolve-radiant-install-hydrator.js +3 -0
- package/CHANGELOG.md +0 -15
- package/src/services/jsx-runtime-bundle.service.d.ts +0 -51
- package/src/services/jsx-runtime-bundle.service.js +0 -206
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
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 (!existsSync(scriptPath)) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
const content = readFileSync(scriptPath, 'utf8');
|
|
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
|
+
}
|
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
|
-
};
|