@ecopages/ecopages-jsx 0.2.0-alpha.25 → 0.2.0-alpha.27
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 +9 -5
- package/README.md +12 -3
- package/package.json +4 -4
- package/src/ecopages-jsx-mdx.d.ts +21 -0
- package/src/ecopages-jsx-mdx.js +113 -0
- package/src/ecopages-jsx-radiant-ssr-policy.d.ts +33 -0
- package/src/ecopages-jsx-radiant-ssr-policy.js +80 -0
- package/src/ecopages-jsx-render-session.d.ts +39 -0
- package/src/ecopages-jsx-render-session.js +78 -0
- package/src/ecopages-jsx-renderer.d.ts +11 -54
- package/src/ecopages-jsx-renderer.js +117 -330
- package/src/ecopages-jsx.plugin.d.ts +4 -33
- package/src/ecopages-jsx.plugin.js +21 -196
- package/src/ecopages-jsx.types.d.ts +6 -10
- package/src/services/jsx-runtime-bundle.service.d.ts +0 -54
- package/src/services/jsx-runtime-bundle.service.js +0 -251
|
@@ -1,65 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { AssetFactory } from "@ecopages/core/services/asset-processing-service";
|
|
5
|
-
import { VFile } from "vfile";
|
|
1
|
+
import {
|
|
2
|
+
IntegrationPlugin
|
|
3
|
+
} from "@ecopages/core/plugins/integration-plugin";
|
|
6
4
|
import { ECOPAGES_JSX_PLUGIN_NAME } from "./ecopages-jsx.constants.js";
|
|
5
|
+
import {
|
|
6
|
+
appendMdxExtensions,
|
|
7
|
+
createMdxLoaderPlugin,
|
|
8
|
+
registerBunMdxPlugin,
|
|
9
|
+
resolveMdxCompilerOptions
|
|
10
|
+
} from "./ecopages-jsx-mdx.js";
|
|
7
11
|
import { EcopagesJsxRenderer } from "./ecopages-jsx-renderer.js";
|
|
8
|
-
import { JsxRuntimeBundleService } from "./services/jsx-runtime-bundle.service.js";
|
|
9
|
-
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10
|
-
const mergePluginLists = (...lists) => {
|
|
11
|
-
const merged = lists.flatMap((list) => list ? [...list] : []);
|
|
12
|
-
return merged.length > 0 ? merged : void 0;
|
|
13
|
-
};
|
|
14
|
-
const createMdxExtensionFilter = (extensions, options) => {
|
|
15
|
-
const escaped = extensions.map(escapeRegex);
|
|
16
|
-
const suffix = options?.allowQueryString ? "(\\?.*)?$" : "$";
|
|
17
|
-
return new RegExp(`(${escaped.join("|")})${suffix}`);
|
|
18
|
-
};
|
|
19
|
-
const appendMdxExtensions = (target, mdxExtensions) => {
|
|
20
|
-
for (const ext of mdxExtensions) {
|
|
21
|
-
if (!target.includes(ext)) {
|
|
22
|
-
target.push(ext);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
const resolveMdxCompilerOptions = (mdxOptions) => {
|
|
27
|
-
const { compilerOptions, remarkPlugins, rehypePlugins, recmaPlugins } = mdxOptions;
|
|
28
|
-
const resolved = {
|
|
29
|
-
format: "detect",
|
|
30
|
-
outputFormat: "program",
|
|
31
|
-
...compilerOptions,
|
|
32
|
-
jsxImportSource: "@ecopages/jsx",
|
|
33
|
-
jsxRuntime: "automatic",
|
|
34
|
-
development: process.env.NODE_ENV === "development"
|
|
35
|
-
};
|
|
36
|
-
const mergedRemark = mergePluginLists(compilerOptions?.remarkPlugins, remarkPlugins);
|
|
37
|
-
const mergedRehype = mergePluginLists(compilerOptions?.rehypePlugins, rehypePlugins);
|
|
38
|
-
const mergedRecma = mergePluginLists(compilerOptions?.recmaPlugins, recmaPlugins);
|
|
39
|
-
if (mergedRemark) resolved.remarkPlugins = mergedRemark;
|
|
40
|
-
if (mergedRehype) resolved.rehypePlugins = mergedRehype;
|
|
41
|
-
if (mergedRecma) resolved.recmaPlugins = mergedRecma;
|
|
42
|
-
return resolved;
|
|
43
|
-
};
|
|
44
|
-
const createMdxLoaderPlugin = (compilerOptions, extensions) => {
|
|
45
|
-
const filter = createMdxExtensionFilter(extensions, { allowQueryString: true });
|
|
46
|
-
return {
|
|
47
|
-
name: "ecopages-jsx-mdx-loader",
|
|
48
|
-
setup(build) {
|
|
49
|
-
build.onLoad({ filter }, async (args) => {
|
|
50
|
-
const { compile } = await import("@mdx-js/mdx");
|
|
51
|
-
const filePath = args.path.includes("?") ? args.path.split("?")[0] : args.path;
|
|
52
|
-
const source = await readFile(filePath, "utf-8");
|
|
53
|
-
const compiled = await compile(new VFile({ value: source, path: filePath }), compilerOptions);
|
|
54
|
-
return {
|
|
55
|
-
contents: String(compiled.value),
|
|
56
|
-
loader: "js",
|
|
57
|
-
resolveDir: path.dirname(filePath)
|
|
58
|
-
};
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
};
|
|
63
12
|
const resolvePluginOptions = (options) => {
|
|
64
13
|
const { extensions: userExtensions, radiant, mdx, ...baseConfig } = options ?? {};
|
|
65
14
|
const extensions = [...userExtensions ?? [".tsx"]];
|
|
@@ -79,49 +28,26 @@ const resolvePluginOptions = (options) => {
|
|
|
79
28
|
};
|
|
80
29
|
class EcopagesJsxPlugin extends IntegrationPlugin {
|
|
81
30
|
renderer = EcopagesJsxRenderer;
|
|
82
|
-
customElementAssets = /* @__PURE__ */ new Map();
|
|
83
|
-
customElementScriptFiles = /* @__PURE__ */ new Map();
|
|
84
31
|
includeRadiant;
|
|
85
32
|
mdxEnabled;
|
|
86
33
|
mdxCompilerOptions;
|
|
87
34
|
mdxExtensions;
|
|
88
35
|
mdxLoaderPlugin;
|
|
89
|
-
runtimeBundleService;
|
|
90
|
-
runtimeSpecifierMap = {};
|
|
91
|
-
runtimeDepsInitialized = false;
|
|
92
36
|
/** Returns the build plugins required by the JSX integration. */
|
|
93
37
|
get plugins() {
|
|
94
38
|
return [this.mdxLoaderPlugin].filter((plugin) => plugin !== void 0);
|
|
95
39
|
}
|
|
96
|
-
/**
|
|
97
|
-
get browserBuildPlugins() {
|
|
98
|
-
return [this.runtimeBundleService.getBuildPlugin()];
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Exposes the bare-module specifier map used by the import map.
|
|
102
|
-
*
|
|
103
|
-
* Client bundles keep these imports external so the browser can load the
|
|
104
|
-
* shared runtime packages from the generated vendor assets.
|
|
105
|
-
*/
|
|
106
|
-
getRuntimeSpecifierMap() {
|
|
107
|
-
return this.runtimeSpecifierMap;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Creates the renderer instance and attaches the discovered intrinsic custom
|
|
111
|
-
* element assets before the renderer handles any requests.
|
|
112
|
-
*/
|
|
40
|
+
/** Creates the renderer instance with the resolved JSX integration runtime options. */
|
|
113
41
|
initializeRenderer(options) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
124
|
-
return this.attachRendererRuntimeServices(renderer);
|
|
42
|
+
return this.attachRendererRuntimeServices(
|
|
43
|
+
new this.renderer({
|
|
44
|
+
...this.createRendererOptions(options),
|
|
45
|
+
jsxConfig: {
|
|
46
|
+
mdxExtensions: this.mdxExtensions,
|
|
47
|
+
radiantSsrEnabled: this.includeRadiant
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
);
|
|
125
51
|
}
|
|
126
52
|
constructor(options) {
|
|
127
53
|
const config = resolvePluginOptions(options);
|
|
@@ -133,32 +59,22 @@ class EcopagesJsxPlugin extends IntegrationPlugin {
|
|
|
133
59
|
...baseConfig
|
|
134
60
|
});
|
|
135
61
|
this.includeRadiant = includeRadiant;
|
|
136
|
-
this.runtimeBundleService = new JsxRuntimeBundleService({ radiant: includeRadiant });
|
|
137
62
|
this.mdxEnabled = mdxEnabled;
|
|
138
63
|
this.mdxExtensions = mdxExtensions;
|
|
139
64
|
this.mdxCompilerOptions = mdxCompilerOptions;
|
|
140
65
|
}
|
|
141
66
|
/** Ensures MDX build hooks are ready before Ecopages collects contributions. */
|
|
142
67
|
async prepareBuildContributions() {
|
|
143
|
-
if (!this.runtimeDepsInitialized) {
|
|
144
|
-
this.runtimeDepsInitialized = true;
|
|
145
|
-
this.runtimeBundleService.setRootDir(this.appConfig?.rootDir);
|
|
146
|
-
this.runtimeSpecifierMap = await this.runtimeBundleService.getSpecifierMap();
|
|
147
|
-
const vendorDeps = await this.runtimeBundleService.getDependencies();
|
|
148
|
-
this.integrationDependencies.unshift(...vendorDeps);
|
|
149
|
-
}
|
|
150
68
|
this.ensureMdxLoaderPlugin();
|
|
151
69
|
}
|
|
152
70
|
/**
|
|
153
|
-
* Registers MDX tooling
|
|
154
|
-
* completes the base integration setup.
|
|
71
|
+
* Registers MDX tooling and completes the base integration setup.
|
|
155
72
|
*/
|
|
156
73
|
async setup() {
|
|
157
74
|
this.ensureMdxLoaderPlugin();
|
|
158
75
|
if (typeof Bun !== "undefined" && this.mdxEnabled && this.mdxCompilerOptions) {
|
|
159
76
|
await this.registerMdxBunPlugin();
|
|
160
77
|
}
|
|
161
|
-
await this.buildCustomElementRegistry();
|
|
162
78
|
await super.setup();
|
|
163
79
|
}
|
|
164
80
|
ensureMdxLoaderPlugin() {
|
|
@@ -177,98 +93,7 @@ class EcopagesJsxPlugin extends IntegrationPlugin {
|
|
|
177
93
|
if (typeof Bun === "undefined" || !this.mdxCompilerOptions) {
|
|
178
94
|
return;
|
|
179
95
|
}
|
|
180
|
-
|
|
181
|
-
const filter = createMdxExtensionFilter(this.mdxExtensions);
|
|
182
|
-
Bun.plugin({
|
|
183
|
-
name: "ecopages-jsx-mdx",
|
|
184
|
-
setup(build) {
|
|
185
|
-
build.onLoad({ filter }, async (args) => {
|
|
186
|
-
const { compile } = await import("@mdx-js/mdx");
|
|
187
|
-
const source = await readFile(args.path, "utf-8");
|
|
188
|
-
const compiled = await compile(new VFile({ value: source, path: args.path }), compilerOptions);
|
|
189
|
-
return { contents: String(compiled.value), loader: "js" };
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Scans `src/` for custom-element entry scripts and pre-resolves their assets.
|
|
196
|
-
*
|
|
197
|
-
* The renderer's server-side custom-element hook relies on this registry to
|
|
198
|
-
* attach browser scripts without per-render file-system lookups.
|
|
199
|
-
*/
|
|
200
|
-
async buildCustomElementRegistry() {
|
|
201
|
-
if (!this.appConfig || !this.assetProcessingService) {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
this.customElementAssets.clear();
|
|
205
|
-
this.customElementScriptFiles.clear();
|
|
206
|
-
const scriptFiles = await this.collectScriptEntries(this.appConfig.absolutePaths.srcDir);
|
|
207
|
-
for (const scriptFile of scriptFiles) {
|
|
208
|
-
const tagNames = await this.extractCustomElementTagNames(scriptFile);
|
|
209
|
-
if (tagNames.length === 0) {
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
const asset = await this.resolveCustomElementAsset(scriptFile);
|
|
213
|
-
if (!asset) {
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
for (const tagName of tagNames) {
|
|
217
|
-
this.customElementAssets.set(tagName, [asset]);
|
|
218
|
-
this.customElementScriptFiles.set(tagName, scriptFile);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
async collectScriptEntries(directory) {
|
|
223
|
-
const entries = await readdir(directory, { withFileTypes: true });
|
|
224
|
-
const scripts = [];
|
|
225
|
-
for (const entry of entries) {
|
|
226
|
-
const entryPath = path.join(directory, entry.name);
|
|
227
|
-
if (entry.isDirectory()) {
|
|
228
|
-
scripts.push(...await this.collectScriptEntries(entryPath));
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
if (/\.script\.(?:ts|tsx)$/.test(entry.name)) {
|
|
232
|
-
scripts.push(entryPath);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return scripts;
|
|
236
|
-
}
|
|
237
|
-
async resolveCustomElementAsset(scriptFile) {
|
|
238
|
-
if (!this.assetProcessingService) {
|
|
239
|
-
return void 0;
|
|
240
|
-
}
|
|
241
|
-
const [asset] = await this.assetProcessingService.processDependencies(
|
|
242
|
-
[
|
|
243
|
-
AssetFactory.createFileScript({
|
|
244
|
-
filepath: scriptFile,
|
|
245
|
-
position: "head",
|
|
246
|
-
attributes: {
|
|
247
|
-
type: "module",
|
|
248
|
-
defer: ""
|
|
249
|
-
}
|
|
250
|
-
})
|
|
251
|
-
],
|
|
252
|
-
`${this.name}:custom-elements:${scriptFile}`
|
|
253
|
-
);
|
|
254
|
-
return asset;
|
|
255
|
-
}
|
|
256
|
-
async extractCustomElementTagNames(scriptFile) {
|
|
257
|
-
const source = await readFile(scriptFile, "utf8");
|
|
258
|
-
const tagNames = /* @__PURE__ */ new Set();
|
|
259
|
-
for (const match of source.matchAll(/@customElement\(\s*['"]([^'"]+)['"]/g)) {
|
|
260
|
-
const tagName = match[1];
|
|
261
|
-
if (tagName) {
|
|
262
|
-
tagNames.add(tagName);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
for (const match of source.matchAll(/customElement\(\s*['"]([^'"]+)['"]\s*\)\s*\(/g)) {
|
|
266
|
-
const tagName = match[1];
|
|
267
|
-
if (tagName) {
|
|
268
|
-
tagNames.add(tagName);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return [...tagNames];
|
|
96
|
+
await registerBunMdxPlugin(this.mdxCompilerOptions, this.mdxExtensions);
|
|
272
97
|
}
|
|
273
98
|
}
|
|
274
99
|
const ecopagesJsxPlugin = (options) => new EcopagesJsxPlugin(options);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { CompileOptions } from '@mdx-js/mdx';
|
|
2
2
|
import type { IntegrationPluginConfig } from '@ecopages/core/plugins/integration-plugin';
|
|
3
3
|
import type { AssetDefinition, AssetProcessingService, ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
|
|
4
|
-
import type { EcoPagesAppConfig } from '@ecopages/core
|
|
4
|
+
import type { EcoPagesAppConfig } from '@ecopages/core';
|
|
5
|
+
import type { EcopagesJsxRadiantSsrPolicy } from './ecopages-jsx-radiant-ssr-policy.js';
|
|
5
6
|
type MdxPluginList = NonNullable<CompileOptions['remarkPlugins']>;
|
|
6
7
|
export type EcopagesJsxMdxCompileOptions = Omit<CompileOptions, 'jsxImportSource' | 'jsxRuntime' | 'remarkPlugins' | 'rehypePlugins' | 'recmaPlugins'> & {
|
|
7
8
|
remarkPlugins?: MdxPluginList;
|
|
@@ -33,14 +34,10 @@ export type EcopagesJsxPluginOptions = Omit<IntegrationPluginConfig, 'name' | 'e
|
|
|
33
34
|
/** Optional JSX route extensions. Defaults to `.tsx`. */
|
|
34
35
|
extensions?: string[];
|
|
35
36
|
/**
|
|
36
|
-
* Whether to
|
|
37
|
+
* Whether to enable the Radiant SSR contract for JSX apps.
|
|
37
38
|
*
|
|
38
|
-
* When enabled, Ecopages JSX
|
|
39
|
-
*
|
|
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
|
|
39
|
+
* When enabled, Ecopages JSX installs the Radiant server SSR runtime so
|
|
40
|
+
* intrinsic custom elements can render specialized host markup during SSR.
|
|
44
41
|
*
|
|
45
42
|
* Set to `false` when pages do not use Radiant web components.
|
|
46
43
|
* @default true
|
|
@@ -50,9 +47,8 @@ export type EcopagesJsxPluginOptions = Omit<IntegrationPluginConfig, 'name' | 'e
|
|
|
50
47
|
mdx?: EcopagesJsxMdxOptions;
|
|
51
48
|
};
|
|
52
49
|
export type EcopagesJsxRendererConfig = {
|
|
53
|
-
intrinsicCustomElementAssets?: Map<string, readonly ProcessedAsset[]>;
|
|
54
|
-
intrinsicCustomElementScriptFiles?: Map<string, string>;
|
|
55
50
|
mdxExtensions?: string[];
|
|
51
|
+
radiantSsrPolicy?: EcopagesJsxRadiantSsrPolicy;
|
|
56
52
|
radiantSsrEnabled?: boolean;
|
|
57
53
|
};
|
|
58
54
|
export type EcopagesJsxRendererOptions = {
|
|
@@ -1,54 +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 cachedJsxEntryModulePath;
|
|
21
|
-
private cachedRadiantEntryModulePath;
|
|
22
|
-
constructor(config: JsxRuntimeBundleServiceConfig);
|
|
23
|
-
setRootDir(rootDir: string | undefined): void;
|
|
24
|
-
/**
|
|
25
|
-
* Returns the build plugin that aliases JSX and Radiant runtime specifiers to
|
|
26
|
-
* their emitted browser vendor URLs.
|
|
27
|
-
*
|
|
28
|
-
* @remarks
|
|
29
|
-
* The returned plugin both externalizes the mapped specifiers during bundle
|
|
30
|
-
* resolution and exposes alias metadata so Ecopages can rewrite any emitted JS
|
|
31
|
-
* imports that still reference bare runtime specifiers.
|
|
32
|
-
*/
|
|
33
|
-
getBuildPlugin(): EcoBuildPlugin;
|
|
34
|
-
/**
|
|
35
|
-
* Builds the bare-specifier-to-vendor-URL map for the browser import map.
|
|
36
|
-
*
|
|
37
|
-
* JSX sub-paths are always included. When `radiant` is enabled, radiant
|
|
38
|
-
* sub-paths are derived from `@ecopages/radiant/package.json` exports and
|
|
39
|
-
* the result is cached for the lifetime of this service instance.
|
|
40
|
-
*/
|
|
41
|
-
getSpecifierMap(): Promise<Record<string, string>>;
|
|
42
|
-
/**
|
|
43
|
-
* Builds the full list of vendor asset definitions: the import map inline
|
|
44
|
-
* assets plus one `createBrowserRuntimeScriptAsset` per vendor bundle.
|
|
45
|
-
*/
|
|
46
|
-
getDependencies(): Promise<AssetDefinition[]>;
|
|
47
|
-
private getArtifactsDir;
|
|
48
|
-
private getEntryImportPath;
|
|
49
|
-
private getOrCreateSpecifierMap;
|
|
50
|
-
private getOrCreateJsxEntryModulePath;
|
|
51
|
-
private getRadiantBrowserRuntimeModules;
|
|
52
|
-
private resolvePackageExportModulePath;
|
|
53
|
-
private getOrCreateRadiantEntryModulePath;
|
|
54
|
-
}
|
|
@@ -1,251 +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
|
-
} from "@ecopages/core/services/asset-processing-service";
|
|
8
|
-
const VENDOR_FILE_NAMES = {
|
|
9
|
-
jsx: "ecopages-jsx-esm.js",
|
|
10
|
-
radiant: "ecopages-radiant-esm.js"
|
|
11
|
-
};
|
|
12
|
-
function getNamedExportNamesFromModuleSource(source) {
|
|
13
|
-
const exportNames = /* @__PURE__ */ new Set();
|
|
14
|
-
for (const match of source.matchAll(/export\s*\{([^}]+)\}/g)) {
|
|
15
|
-
for (const specifier of match[1].split(",")) {
|
|
16
|
-
const trimmedSpecifier = specifier.trim();
|
|
17
|
-
if (!trimmedSpecifier) {
|
|
18
|
-
continue;
|
|
19
|
-
}
|
|
20
|
-
const aliasMatch = trimmedSpecifier.match(/(?:.+\s+as\s+)?([A-Z_a-z$][\w$]*)$/);
|
|
21
|
-
if (aliasMatch?.[1] && aliasMatch[1] !== "default") {
|
|
22
|
-
exportNames.add(aliasMatch[1]);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
for (const match of source.matchAll(
|
|
27
|
-
/export\s+(?:async\s+)?(?:const|function|class|let|var)\s+([A-Z_a-z$][\w$]*)/g
|
|
28
|
-
)) {
|
|
29
|
-
if (match[1] !== "default") {
|
|
30
|
-
exportNames.add(match[1]);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return [...exportNames].sort();
|
|
34
|
-
}
|
|
35
|
-
function isBrowserRuntimeRadiantSpecifier(exportKey) {
|
|
36
|
-
if (exportKey === "." || exportKey.startsWith("./context/")) {
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
if (exportKey === "./controller-registry") {
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
if (exportKey === "./client/hydrator") {
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
if (exportKey === "./client/install-hydrator") {
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
if (exportKey.startsWith("./decorators/") || exportKey.startsWith("./helpers/")) {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
return exportKey === "./core/radiant-controller" || exportKey === "./core/radiant-element";
|
|
52
|
-
}
|
|
53
|
-
function isObjectRecord(value) {
|
|
54
|
-
return typeof value === "object" && value !== null;
|
|
55
|
-
}
|
|
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
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function findPackageManifestPath(packageName) {
|
|
69
|
-
let currentDir = path.dirname(new URL(import.meta.url).pathname);
|
|
70
|
-
while (true) {
|
|
71
|
-
const candidatePath = path.join(currentDir, "node_modules", packageName, "package.json");
|
|
72
|
-
if (existsSync(candidatePath)) {
|
|
73
|
-
return candidatePath;
|
|
74
|
-
}
|
|
75
|
-
const parentDir = path.dirname(currentDir);
|
|
76
|
-
if (parentDir === currentDir) {
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
currentDir = parentDir;
|
|
80
|
-
}
|
|
81
|
-
throw new Error(`Could not locate ${packageName}/package.json from ${import.meta.url}`);
|
|
82
|
-
}
|
|
83
|
-
class JsxRuntimeBundleService {
|
|
84
|
-
config;
|
|
85
|
-
cachedSpecifierMap;
|
|
86
|
-
cachedJsxEntryModulePath;
|
|
87
|
-
cachedRadiantEntryModulePath;
|
|
88
|
-
constructor(config) {
|
|
89
|
-
this.config = config;
|
|
90
|
-
}
|
|
91
|
-
setRootDir(rootDir) {
|
|
92
|
-
this.config.rootDir = rootDir;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Returns the build plugin that aliases JSX and Radiant runtime specifiers to
|
|
96
|
-
* their emitted browser vendor URLs.
|
|
97
|
-
*
|
|
98
|
-
* @remarks
|
|
99
|
-
* The returned plugin both externalizes the mapped specifiers during bundle
|
|
100
|
-
* resolution and exposes alias metadata so Ecopages can rewrite any emitted JS
|
|
101
|
-
* imports that still reference bare runtime specifiers.
|
|
102
|
-
*/
|
|
103
|
-
getBuildPlugin() {
|
|
104
|
-
return createRuntimeSpecifierAliasPlugin(this.getOrCreateSpecifierMap(), {
|
|
105
|
-
name: "ecopages-jsx-runtime-alias"
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Builds the bare-specifier-to-vendor-URL map for the browser import map.
|
|
110
|
-
*
|
|
111
|
-
* JSX sub-paths are always included. When `radiant` is enabled, radiant
|
|
112
|
-
* sub-paths are derived from `@ecopages/radiant/package.json` exports and
|
|
113
|
-
* the result is cached for the lifetime of this service instance.
|
|
114
|
-
*/
|
|
115
|
-
async getSpecifierMap() {
|
|
116
|
-
return this.getOrCreateSpecifierMap();
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Builds the full list of vendor asset definitions: the import map inline
|
|
120
|
-
* assets plus one `createBrowserRuntimeScriptAsset` per vendor bundle.
|
|
121
|
-
*/
|
|
122
|
-
async getDependencies() {
|
|
123
|
-
const jsxEntryModulePath = await this.getOrCreateJsxEntryModulePath();
|
|
124
|
-
const deps = [];
|
|
125
|
-
deps.push(
|
|
126
|
-
createBrowserRuntimeScriptAsset({
|
|
127
|
-
importPath: jsxEntryModulePath,
|
|
128
|
-
name: "ecopages-jsx-esm",
|
|
129
|
-
fileName: VENDOR_FILE_NAMES.jsx
|
|
130
|
-
})
|
|
131
|
-
);
|
|
132
|
-
if (this.config.radiant) {
|
|
133
|
-
const radiantEntryModulePath = await this.getOrCreateRadiantEntryModulePath();
|
|
134
|
-
deps.push(
|
|
135
|
-
createBrowserRuntimeScriptAsset({
|
|
136
|
-
importPath: radiantEntryModulePath,
|
|
137
|
-
name: "ecopages-radiant-esm",
|
|
138
|
-
fileName: VENDOR_FILE_NAMES.radiant
|
|
139
|
-
})
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
return deps;
|
|
143
|
-
}
|
|
144
|
-
getArtifactsDir() {
|
|
145
|
-
const rootDir = this.config.rootDir ?? process.cwd();
|
|
146
|
-
return path.join(rootDir, "node_modules", ".cache", "ecopages-browser-runtime");
|
|
147
|
-
}
|
|
148
|
-
getEntryImportPath(fromDir, targetPath) {
|
|
149
|
-
const relativeModulePath = path.relative(fromDir, targetPath).split(path.sep).join("/");
|
|
150
|
-
return relativeModulePath.startsWith(".") ? relativeModulePath : `./${relativeModulePath}`;
|
|
151
|
-
}
|
|
152
|
-
getOrCreateSpecifierMap() {
|
|
153
|
-
if (this.cachedSpecifierMap) {
|
|
154
|
-
return this.cachedSpecifierMap;
|
|
155
|
-
}
|
|
156
|
-
const jsxVendorUrl = buildBrowserRuntimeAssetUrl(VENDOR_FILE_NAMES.jsx);
|
|
157
|
-
const specifierMap = {
|
|
158
|
-
"@ecopages/jsx": jsxVendorUrl,
|
|
159
|
-
"@ecopages/jsx/client": jsxVendorUrl,
|
|
160
|
-
"@ecopages/jsx/jsx-runtime": jsxVendorUrl,
|
|
161
|
-
"@ecopages/jsx/jsx-dev-runtime": jsxVendorUrl
|
|
162
|
-
};
|
|
163
|
-
if (this.config.radiant) {
|
|
164
|
-
const radiantVendorUrl = buildBrowserRuntimeAssetUrl(VENDOR_FILE_NAMES.radiant);
|
|
165
|
-
const radiantPkg = readRadiantPackageJson(findPackageManifestPath("@ecopages/radiant"));
|
|
166
|
-
for (const key of Object.keys(radiantPkg.exports ?? {})) {
|
|
167
|
-
if (!isBrowserRuntimeRadiantSpecifier(key)) {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const specifier = key === "." ? "@ecopages/radiant" : `@ecopages/radiant${key.slice(1)}`;
|
|
171
|
-
specifierMap[specifier] = radiantVendorUrl;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
this.cachedSpecifierMap = specifierMap;
|
|
175
|
-
return specifierMap;
|
|
176
|
-
}
|
|
177
|
-
async getOrCreateJsxEntryModulePath() {
|
|
178
|
-
if (this.cachedJsxEntryModulePath) {
|
|
179
|
-
return this.cachedJsxEntryModulePath;
|
|
180
|
-
}
|
|
181
|
-
const artifactsDir = this.getArtifactsDir();
|
|
182
|
-
const filePath = path.join(artifactsDir, "ecopages-jsx-esm-entry.mjs");
|
|
183
|
-
const manifestPath = findPackageManifestPath("@ecopages/jsx");
|
|
184
|
-
const packageDir = path.dirname(realpathSync(manifestPath));
|
|
185
|
-
const jsxPkg = readRadiantPackageJson(manifestPath);
|
|
186
|
-
const jsxModulePath = this.resolvePackageExportModulePath(packageDir, ".", jsxPkg.exports?.["."]);
|
|
187
|
-
const jsxRuntimeSource = readFileSync(jsxModulePath, "utf8");
|
|
188
|
-
mkdirSync(artifactsDir, { recursive: true });
|
|
189
|
-
writeFileSync(filePath, jsxRuntimeSource, "utf8");
|
|
190
|
-
this.cachedJsxEntryModulePath = filePath;
|
|
191
|
-
return filePath;
|
|
192
|
-
}
|
|
193
|
-
getRadiantBrowserRuntimeModules() {
|
|
194
|
-
const manifestPath = findPackageManifestPath("@ecopages/radiant");
|
|
195
|
-
const packageDir = path.dirname(realpathSync(manifestPath));
|
|
196
|
-
const radiantPkg = readRadiantPackageJson(manifestPath);
|
|
197
|
-
return Object.entries(radiantPkg.exports ?? {}).filter(([key]) => isBrowserRuntimeRadiantSpecifier(key) && key !== ".").sort(([left], [right]) => left.localeCompare(right)).map(([exportKey, exportTarget]) => ({
|
|
198
|
-
exportKey,
|
|
199
|
-
modulePath: this.resolvePackageExportModulePath(packageDir, exportKey, exportTarget)
|
|
200
|
-
})).filter((module) => existsSync(module.modulePath));
|
|
201
|
-
}
|
|
202
|
-
resolvePackageExportModulePath(packageDir, exportKey, exportTarget) {
|
|
203
|
-
if (typeof exportTarget === "string") {
|
|
204
|
-
return path.resolve(packageDir, exportTarget);
|
|
205
|
-
}
|
|
206
|
-
if (isObjectRecord(exportTarget) && "import" in exportTarget) {
|
|
207
|
-
const importTarget = exportTarget.import;
|
|
208
|
-
if (typeof importTarget === "string") {
|
|
209
|
-
return path.resolve(packageDir, importTarget);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
throw new Error(`Missing import target for @ecopages/radiant export ${exportKey}`);
|
|
213
|
-
}
|
|
214
|
-
async getOrCreateRadiantEntryModulePath() {
|
|
215
|
-
if (this.cachedRadiantEntryModulePath) {
|
|
216
|
-
return this.cachedRadiantEntryModulePath;
|
|
217
|
-
}
|
|
218
|
-
const artifactsDir = this.getArtifactsDir();
|
|
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
|
-
);
|
|
228
|
-
const seenExports = /* @__PURE__ */ new Set();
|
|
229
|
-
const statements = [
|
|
230
|
-
`import '${this.getEntryImportPath(artifactsDir, installHydratorModulePath)}';`
|
|
231
|
-
];
|
|
232
|
-
mkdirSync(artifactsDir, { recursive: true });
|
|
233
|
-
for (const module of this.getRadiantBrowserRuntimeModules()) {
|
|
234
|
-
const exportNames = getNamedExportNamesFromModuleSource(readFileSync(module.modulePath, "utf8")).filter((name) => !seenExports.has(name)).filter((name) => /^[$A-Z_a-z][$\w]*$/.test(name)).sort();
|
|
235
|
-
if (exportNames.length === 0) {
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
const entryImportPath = this.getEntryImportPath(artifactsDir, module.modulePath);
|
|
239
|
-
statements.push(`export { ${exportNames.join(", ")} } from '${entryImportPath}';`);
|
|
240
|
-
for (const exportName of exportNames) {
|
|
241
|
-
seenExports.add(exportName);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
writeFileSync(filePath, statements.join("\n"), "utf8");
|
|
245
|
-
this.cachedRadiantEntryModulePath = filePath;
|
|
246
|
-
return filePath;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
export {
|
|
250
|
-
JsxRuntimeBundleService
|
|
251
|
-
};
|