@ecopages/ecopages-jsx 0.2.1-beta.0 → 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 +39 -16
- package/package.json +10 -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.js +8 -10
- 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 +22 -11
- package/src/ecopages-jsx-mdx.js +43 -108
- package/src/ecopages-jsx-radiant-ssr-policy.d.ts +12 -6
- package/src/ecopages-jsx-radiant-ssr-policy.js +116 -73
- package/src/ecopages-jsx-renderer.d.ts +10 -45
- package/src/ecopages-jsx-renderer.js +148 -233
- package/src/ecopages-jsx.constants.js +7 -4
- package/src/ecopages-jsx.plugin.d.ts +19 -23
- package/src/ecopages-jsx.plugin.js +137 -111
- package/src/ecopages-jsx.types.d.ts +4 -34
- 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/src/ecopages-jsx-render-session.d.ts +0 -46
- package/src/ecopages-jsx-render-session.js +0 -93
|
@@ -1,120 +1,146 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
} from
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
import { IntegrationPlugin, } from '@ecopages/core/integration';
|
|
2
|
+
import { AssetFactory } from '@ecopages/core/integration';
|
|
3
|
+
import { requireAppRuntime } from '@ecopages/core/integration';
|
|
4
|
+
import { ECOPAGES_JSX_PLUGIN_NAME } from './ecopages-jsx.constants.js';
|
|
5
|
+
import { RADIANT_INSTALL_HYDRATOR_FILEPATH } from './resolve-radiant-install-hydrator.js';
|
|
6
|
+
import { appendMdxExtensions, createMdxLoaderPlugin, resolveMdxCompilerOptions, } from './ecopages-jsx-mdx.js';
|
|
7
|
+
import { createEcopagesJsxRenderer, createEcopagesJsxRendererHookFactory, EcopagesJsxRenderer, } from './ecopages-jsx-renderer.js';
|
|
8
|
+
import { EcopagesJsxHmrStrategy } from './ecopages-jsx-hmr-strategy.js';
|
|
9
|
+
import { EcopagesJsxDevTransformContributor } from './dev-transform/ecopages-jsx-dev-transform-contributor.js';
|
|
10
|
+
import { assignPageOwnedContentScriptGroupedBundles } from './page-owned-content-script-grouping.js';
|
|
11
|
+
import { invalidateRadiantRegisteredScriptSsrRegistration } from './radiant-registered-script-ssr-invalidation.js';
|
|
12
|
+
const RADIANT_HYDRATOR_SCRIPT_ID = 'ecopages-jsx-radiant-hydrator';
|
|
13
|
+
/**
|
|
14
|
+
* Resolves user-facing plugin options into a fully-defaulted internal config.
|
|
15
|
+
*
|
|
16
|
+
* Defaults:
|
|
17
|
+
* - `extensions`: `['.tsx']`
|
|
18
|
+
* - `radiant`: `true`
|
|
19
|
+
* - MDX is disabled when `mdx` is omitted
|
|
20
|
+
* - `mdx.extensions`: `['.mdx']`
|
|
21
|
+
*/
|
|
14
22
|
const resolvePluginOptions = (options) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
};
|
|
23
|
+
const { extensions: userExtensions, radiant, mdx, ...baseConfig } = options ?? {};
|
|
24
|
+
const extensions = [...(userExtensions ?? ['.tsx'])];
|
|
25
|
+
const mdxExtensions = mdx?.extensions ?? ['.mdx'];
|
|
26
|
+
const mdxEnabled = mdx !== undefined;
|
|
27
|
+
if (mdxEnabled) {
|
|
28
|
+
appendMdxExtensions(extensions, mdxExtensions);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
...baseConfig,
|
|
32
|
+
extensions,
|
|
33
|
+
includeRadiant: radiant ?? true,
|
|
34
|
+
mdxExtensions,
|
|
35
|
+
mdxCompilerOptions: mdxEnabled && mdx ? resolveMdxCompilerOptions(mdx) : undefined,
|
|
36
|
+
};
|
|
30
37
|
};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
mdxExtensions: this.mdxExtensions,
|
|
49
|
-
radiantSsrEnabled: this.includeRadiant
|
|
38
|
+
/** JSX integration plugin for Ecopages, supporting `.tsx` templates and optional Radiant web components. */
|
|
39
|
+
export class EcopagesJsxPlugin extends IntegrationPlugin {
|
|
40
|
+
renderer = EcopagesJsxRenderer;
|
|
41
|
+
bundlesPageDependencies = true;
|
|
42
|
+
includeRadiant;
|
|
43
|
+
mdxCompilerOptions;
|
|
44
|
+
mdxExtensions;
|
|
45
|
+
/**
|
|
46
|
+
* Returns the build plugins required by the JSX integration.
|
|
47
|
+
*
|
|
48
|
+
* @remarks
|
|
49
|
+
* Returns a new loader declaration without registering or initializing it. The build manifest owns the
|
|
50
|
+
* resulting instance for the app runtime.
|
|
51
|
+
*/
|
|
52
|
+
plugins(app) {
|
|
53
|
+
if (!this.mdxCompilerOptions) {
|
|
54
|
+
return [];
|
|
50
55
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
56
|
+
return [
|
|
57
|
+
createMdxLoaderPlugin({
|
|
58
|
+
compilerOptions: this.mdxCompilerOptions,
|
|
59
|
+
extensions: this.mdxExtensions,
|
|
60
|
+
projectRoot: app.rootDir,
|
|
61
|
+
requestPipelineMetrics: requireAppRuntime(app).requestPipelineMetrics,
|
|
62
|
+
}),
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
/** Creates the renderer instance with the resolved JSX integration runtime options. */
|
|
66
|
+
createRenderer(base) {
|
|
67
|
+
const jsxConfig = {
|
|
68
|
+
mdxExtensions: this.mdxExtensions,
|
|
69
|
+
radiantSsrEnabled: this.includeRadiant,
|
|
70
|
+
};
|
|
71
|
+
const customRendererOptions = { ...base, jsxConfig };
|
|
72
|
+
const renderer = this.renderer === EcopagesJsxRenderer
|
|
73
|
+
? new (createEcopagesJsxRenderer(jsxConfig))(base)
|
|
74
|
+
: new this.renderer({
|
|
75
|
+
...customRendererOptions,
|
|
76
|
+
hooks: createEcopagesJsxRendererHookFactory(jsxConfig),
|
|
77
|
+
});
|
|
78
|
+
renderer.name ||= this.name;
|
|
79
|
+
return renderer;
|
|
69
80
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
constructor(options) {
|
|
82
|
+
const config = resolvePluginOptions(options);
|
|
83
|
+
const { extensions, includeRadiant, mdxExtensions, mdxCompilerOptions, ...baseConfig } = config;
|
|
84
|
+
super({
|
|
85
|
+
name: ECOPAGES_JSX_PLUGIN_NAME,
|
|
86
|
+
extensions,
|
|
87
|
+
jsxImportSource: '@ecopages/jsx',
|
|
88
|
+
...baseConfig,
|
|
89
|
+
});
|
|
90
|
+
this.includeRadiant = includeRadiant;
|
|
91
|
+
this.mdxExtensions = mdxExtensions;
|
|
92
|
+
this.mdxCompilerOptions = mdxCompilerOptions;
|
|
93
|
+
if (this.includeRadiant) {
|
|
94
|
+
this.integrationDependencies.unshift(...this.getDependencies());
|
|
79
95
|
}
|
|
80
|
-
})
|
|
81
|
-
];
|
|
82
|
-
}
|
|
83
|
-
/** Ensures MDX build hooks are ready before Ecopages collects contributions. */
|
|
84
|
-
async prepareBuildContributions() {
|
|
85
|
-
this.ensureMdxLoaderPlugin();
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Registers MDX tooling and completes the base integration setup.
|
|
89
|
-
*/
|
|
90
|
-
async setup() {
|
|
91
|
-
this.ensureMdxLoaderPlugin();
|
|
92
|
-
if (typeof Bun !== "undefined" && this.mdxEnabled && this.mdxCompilerOptions) {
|
|
93
|
-
await this.registerMdxBunPlugin();
|
|
94
96
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
getDependencies() {
|
|
98
|
+
return [
|
|
99
|
+
AssetFactory.createNodeModuleScript({
|
|
100
|
+
position: 'head',
|
|
101
|
+
importPath: RADIANT_INSTALL_HYDRATOR_FILEPATH,
|
|
102
|
+
bundle: false,
|
|
103
|
+
attributes: {
|
|
104
|
+
'data-eco-script-id': RADIANT_HYDRATOR_SCRIPT_ID,
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
/** Assigns grouped-build metadata for page-owned JSX content scripts. */
|
|
110
|
+
prepareAssetDependencies(dependencies) {
|
|
111
|
+
return assignPageOwnedContentScriptGroupedBundles(dependencies);
|
|
100
112
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
/** Registers JSX HMR strategy, dev-transform plugins, and Radiant SSR invalidation hooks. */
|
|
114
|
+
hmr(context) {
|
|
115
|
+
super.hmr(context);
|
|
116
|
+
const manager = context.runtime.hmr;
|
|
117
|
+
if (!manager)
|
|
118
|
+
return;
|
|
119
|
+
const preloadScopes = context.runtime.customElementSsrPreloadScopes;
|
|
120
|
+
const hmrContext = manager.getDefaultContext();
|
|
121
|
+
const absolutePaths = context.appConfig.absolutePaths;
|
|
122
|
+
const strategy = new EcopagesJsxHmrStrategy({
|
|
123
|
+
getRegisteredEntrypoints: hmrContext.getRegisteredEntrypoints,
|
|
124
|
+
getSrcDir: hmrContext.getSrcDir,
|
|
125
|
+
getPagesDir: hmrContext.getPagesDir,
|
|
126
|
+
getLayoutsDir: hmrContext.getLayoutsDir,
|
|
127
|
+
getIncludesDir: () => absolutePaths.includesDir,
|
|
128
|
+
getTemplateExtensions: () => this.extensions,
|
|
129
|
+
});
|
|
130
|
+
manager.registerStrategy(strategy);
|
|
131
|
+
manager.registerDevTransformContributor(new EcopagesJsxDevTransformContributor({
|
|
132
|
+
strategy,
|
|
133
|
+
getMdxLoaderPlugin: () => context.runtime.buildManifest.server.find((plugin) => plugin.name === 'ecopages-jsx-mdx-loader'),
|
|
134
|
+
}));
|
|
135
|
+
if (!this.includeRadiant) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (preloadScopes) {
|
|
139
|
+
manager.onScriptEntrypointChanged?.((scriptPath) => invalidateRadiantRegisteredScriptSsrRegistration(preloadScopes, scriptPath));
|
|
140
|
+
}
|
|
112
141
|
}
|
|
113
|
-
await registerBunMdxPlugin(this.mdxCompilerOptions, this.mdxExtensions);
|
|
114
|
-
}
|
|
115
142
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
};
|
|
143
|
+
/**
|
|
144
|
+
* Creates the JSX integration plugin with resolved defaults.
|
|
145
|
+
*/
|
|
146
|
+
export const ecopagesJsxPlugin = (options) => new EcopagesJsxPlugin(options);
|
|
@@ -1,34 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { IntegrationPluginConfig } from '@ecopages/core/
|
|
3
|
-
import type { AssetDefinition
|
|
4
|
-
import type { EcoPagesAppConfig } from '@ecopages/core';
|
|
5
|
-
import type { EcopagesJsxRadiantSsrPolicy } from './ecopages-jsx-radiant-ssr-policy.js';
|
|
6
|
-
type MdxPluginList = NonNullable<CompileOptions['remarkPlugins']>;
|
|
7
|
-
export type EcopagesJsxMdxCompileOptions = Omit<CompileOptions, 'jsxImportSource' | 'jsxRuntime' | 'remarkPlugins' | 'rehypePlugins' | 'recmaPlugins'> & {
|
|
8
|
-
remarkPlugins?: MdxPluginList;
|
|
9
|
-
rehypePlugins?: MdxPluginList;
|
|
10
|
-
recmaPlugins?: MdxPluginList;
|
|
11
|
-
};
|
|
1
|
+
import type { MdxCompilerOptionsInput } from '@ecopages/mdx/core';
|
|
2
|
+
import type { IntegrationPluginConfig } from '@ecopages/core/integration';
|
|
3
|
+
import type { AssetDefinition } from '@ecopages/core/integration';
|
|
12
4
|
/**
|
|
13
5
|
* MDX configuration for the JSX integration.
|
|
14
6
|
*
|
|
15
7
|
* Mirrors Ecopages' built-in combined integration pattern where a single
|
|
16
8
|
* JSX-capable plugin can own both `.tsx` and `.mdx` route files.
|
|
17
9
|
*/
|
|
18
|
-
export type EcopagesJsxMdxOptions =
|
|
19
|
-
/** Enables MDX file handling inside the JSX integration. */
|
|
20
|
-
enabled: boolean;
|
|
21
|
-
/** Additional MDX compiler options. JSX runtime fields are managed by the integration. */
|
|
22
|
-
compilerOptions?: EcopagesJsxMdxCompileOptions;
|
|
23
|
-
/** Extra remark plugins appended to `compilerOptions.remarkPlugins`. */
|
|
24
|
-
remarkPlugins?: MdxPluginList;
|
|
25
|
-
/** Extra rehype plugins appended to `compilerOptions.rehypePlugins`. */
|
|
26
|
-
rehypePlugins?: MdxPluginList;
|
|
27
|
-
/** Extra recma plugins appended to `compilerOptions.recmaPlugins`. */
|
|
28
|
-
recmaPlugins?: MdxPluginList;
|
|
29
|
-
/** Custom file extensions to treat as MDX. */
|
|
30
|
-
extensions?: string[];
|
|
31
|
-
};
|
|
10
|
+
export type EcopagesJsxMdxOptions = MdxCompilerOptionsInput;
|
|
32
11
|
/** Options for the JSX integration plugin. */
|
|
33
12
|
export type EcopagesJsxPluginOptions = Omit<IntegrationPluginConfig, 'name' | 'extensions'> & {
|
|
34
13
|
/** Optional JSX route extensions. Defaults to `.tsx`. */
|
|
@@ -48,15 +27,6 @@ export type EcopagesJsxPluginOptions = Omit<IntegrationPluginConfig, 'name' | 'e
|
|
|
48
27
|
};
|
|
49
28
|
export type EcopagesJsxRendererConfig = {
|
|
50
29
|
mdxExtensions?: string[];
|
|
51
|
-
radiantSsrPolicy?: EcopagesJsxRadiantSsrPolicy;
|
|
52
30
|
radiantSsrEnabled?: boolean;
|
|
53
31
|
};
|
|
54
|
-
export type EcopagesJsxRendererOptions = {
|
|
55
|
-
appConfig: EcoPagesAppConfig;
|
|
56
|
-
assetProcessingService: AssetProcessingService;
|
|
57
|
-
resolvedIntegrationDependencies: ProcessedAsset[];
|
|
58
|
-
rendererModules?: unknown;
|
|
59
|
-
runtimeOrigin: string;
|
|
60
|
-
jsxConfig?: EcopagesJsxRendererConfig;
|
|
61
|
-
};
|
|
62
32
|
export type { AssetDefinition };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type AssetDefinition } from '@ecopages/core/integration';
|
|
2
|
+
/** Returns the app-wide grouped-build id used for page-owned content scripts. */
|
|
3
|
+
export declare function createPageOwnedContentScriptBundleId(_integrationName?: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* Tags page-owned content scripts in one dependency batch with the app-wide client bundle id.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Component file scripts and integration runtime assets stay ungrouped. Entries that
|
|
9
|
+
* already assign a grouped id are left unchanged.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assignPageOwnedContentScriptGroupedBundles(dependencies: AssetDefinition[]): AssetDefinition[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { APP_BROWSER_CLIENT_BUNDLE_ID, } from '@ecopages/core/integration';
|
|
2
|
+
import { contentHash } from '@ecopages/core/build';
|
|
3
|
+
/** Returns the app-wide grouped-build id used for page-owned content scripts. */
|
|
4
|
+
export function createPageOwnedContentScriptBundleId(_integrationName) {
|
|
5
|
+
return APP_BROWSER_CLIENT_BUNDLE_ID;
|
|
6
|
+
}
|
|
7
|
+
function isPageOwnedGroupableContentScript(dep) {
|
|
8
|
+
if (dep.kind !== 'script' || dep.source !== 'content' || dep.inline) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (dep.groupedBundle?.id) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
if (dep.packageRole === 'keep-separate' || dep.packageRole === 'runtime' || dep.packageRole === 'dynamic-chunk') {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
function createPageOwnedGroupedEntryName(dep, usedEntryNames) {
|
|
20
|
+
const baseName = dep.name ?? `script-${contentHash(dep.content, 16)}`;
|
|
21
|
+
if (!usedEntryNames.has(baseName)) {
|
|
22
|
+
usedEntryNames.add(baseName);
|
|
23
|
+
return baseName;
|
|
24
|
+
}
|
|
25
|
+
let suffix = 2;
|
|
26
|
+
let candidate = `${baseName}-${suffix}`;
|
|
27
|
+
while (usedEntryNames.has(candidate)) {
|
|
28
|
+
suffix += 1;
|
|
29
|
+
candidate = `${baseName}-${suffix}`;
|
|
30
|
+
}
|
|
31
|
+
usedEntryNames.add(candidate);
|
|
32
|
+
return candidate;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Tags page-owned content scripts in one dependency batch with the app-wide client bundle id.
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* Component file scripts and integration runtime assets stay ungrouped. Entries that
|
|
39
|
+
* already assign a grouped id are left unchanged.
|
|
40
|
+
*/
|
|
41
|
+
export function assignPageOwnedContentScriptGroupedBundles(dependencies) {
|
|
42
|
+
const bundleId = APP_BROWSER_CLIENT_BUNDLE_ID;
|
|
43
|
+
const usedEntryNames = new Set();
|
|
44
|
+
for (const dependency of dependencies) {
|
|
45
|
+
if (!isPageOwnedGroupableContentScript(dependency)) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
dependency.groupedBundle = {
|
|
49
|
+
id: bundleId,
|
|
50
|
+
entryName: createPageOwnedGroupedEntryName(dependency, usedEntryNames),
|
|
51
|
+
};
|
|
52
|
+
if (dependency.bundle === false) {
|
|
53
|
+
dependency.bundle = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return dependencies;
|
|
57
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CustomElementScriptPreloadScopeState } from '@ecopages/core/integration';
|
|
2
|
+
/**
|
|
3
|
+
* Reads the custom-element tag name from a registered script module source file.
|
|
4
|
+
*/
|
|
5
|
+
export declare function resolveRadiantCustomElementTag(scriptPath: string): string | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Removes one custom-element definition from the active runtime registry.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* Radiant's `@customElement` decorator and the SSR light-DOM shim both skip
|
|
11
|
+
* re-registration when a tag is already defined. During dev, server requests
|
|
12
|
+
* reuse the same process-global registry, so script edits would otherwise keep
|
|
13
|
+
* serving stale SSR markup until a full dev-server restart.
|
|
14
|
+
*/
|
|
15
|
+
export declare function clearRadiantCustomElementDefinition(tagName: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Clears the SSR custom-element registry entry for one registered Radiant script module.
|
|
18
|
+
*/
|
|
19
|
+
export declare function invalidateRadiantRegisteredScriptSsrRegistration(preloadScopes: Map<string, CustomElementScriptPreloadScopeState>, scriptPath: string): boolean;
|
|
@@ -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
|
+
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
|
|
2
|
-
export declare const ECOPAGES_JSX_SSR_RENDER_STATE_KEY: unique symbol;
|
|
3
|
-
/**
|
|
4
|
-
* Tracks JSX SSR asset-collection state for one active render flow.
|
|
5
|
-
*
|
|
6
|
-
* @remarks
|
|
7
|
-
* The renderer still mirrors this state into `@ecopages/jsx/server` so nested
|
|
8
|
-
* JSX renders and existing SSR-scope probes can observe it, but it also keeps
|
|
9
|
-
* an internal async-local copy. That fallback avoids losing renderer state when
|
|
10
|
-
* host runtimes such as Vite end up loading separate module identities for the
|
|
11
|
-
* JSX server helpers.
|
|
12
|
-
*/
|
|
13
|
-
export declare class EcopagesJsxRenderSession {
|
|
14
|
-
private readonly dedupeProcessedAssets;
|
|
15
|
-
constructor(dedupeProcessedAssets: (assets: ProcessedAsset[]) => ProcessedAsset[]);
|
|
16
|
-
/**
|
|
17
|
-
* Runs one render inside the active session scope.
|
|
18
|
-
*
|
|
19
|
-
* @remarks
|
|
20
|
-
* When a render is already active, the current session state is reused and
|
|
21
|
-
* mirrored back into the JSX SSR scope so nested `renderToString()` calls see
|
|
22
|
-
* the same asset frame stack. When no session exists yet, a new state is
|
|
23
|
-
* created and published through both the internal async-local store and the
|
|
24
|
-
* JSX SSR scope bridge.
|
|
25
|
-
*/
|
|
26
|
-
withActiveScope<T>(render: () => T): T;
|
|
27
|
-
/**
|
|
28
|
-
* Runs work with the current page-level hydrate binding namespace attached to JSX SSR.
|
|
29
|
-
*
|
|
30
|
-
* This lets page, layout, and document renders participate in one client-owned
|
|
31
|
-
* hydration root even though Ecopages composes them through multiple sibling render calls.
|
|
32
|
-
*/
|
|
33
|
-
withHydrationBindingScope<T>(render: () => T): T;
|
|
34
|
-
beginCollectedAssetFrame(): ProcessedAsset[];
|
|
35
|
-
endCollectedAssetFrame(frame: ProcessedAsset[]): ProcessedAsset[];
|
|
36
|
-
recordCollectedAssets(collectedAssets: ProcessedAsset[]): ProcessedAsset[];
|
|
37
|
-
/**
|
|
38
|
-
* Resolves the current render-session state from the local async scope first.
|
|
39
|
-
*
|
|
40
|
-
* @remarks
|
|
41
|
-
* The fallback read from `@ecopages/jsx/server` keeps compatibility with code
|
|
42
|
-
* that still inspects the public SSR scope directly, while the async-local
|
|
43
|
-
* store remains the authoritative source for renderer-owned bookkeeping.
|
|
44
|
-
*/
|
|
45
|
-
private getState;
|
|
46
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import {
|
|
3
|
-
createServerHydrationBindingState,
|
|
4
|
-
getActiveSsrScopeValue,
|
|
5
|
-
withActiveSsrScopeValue,
|
|
6
|
-
withServerHydrationBindingState
|
|
7
|
-
} from "@ecopages/jsx/server";
|
|
8
|
-
const ECOPAGES_JSX_SSR_RENDER_STATE_KEY = /* @__PURE__ */ Symbol.for("@ecopages/ecopages-jsx.ssr-render-state");
|
|
9
|
-
const renderStateStorage = new AsyncLocalStorage();
|
|
10
|
-
class EcopagesJsxRenderSession {
|
|
11
|
-
dedupeProcessedAssets;
|
|
12
|
-
constructor(dedupeProcessedAssets) {
|
|
13
|
-
this.dedupeProcessedAssets = dedupeProcessedAssets;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Runs one render inside the active session scope.
|
|
17
|
-
*
|
|
18
|
-
* @remarks
|
|
19
|
-
* When a render is already active, the current session state is reused and
|
|
20
|
-
* mirrored back into the JSX SSR scope so nested `renderToString()` calls see
|
|
21
|
-
* the same asset frame stack. When no session exists yet, a new state is
|
|
22
|
-
* created and published through both the internal async-local store and the
|
|
23
|
-
* JSX SSR scope bridge.
|
|
24
|
-
*/
|
|
25
|
-
withActiveScope(render) {
|
|
26
|
-
const activeState = renderStateStorage.getStore();
|
|
27
|
-
if (activeState) {
|
|
28
|
-
return withActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY, activeState, render);
|
|
29
|
-
}
|
|
30
|
-
const jsxScopeState = getActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY);
|
|
31
|
-
if (jsxScopeState) {
|
|
32
|
-
return renderStateStorage.run(jsxScopeState, () => render());
|
|
33
|
-
}
|
|
34
|
-
const state = {
|
|
35
|
-
collectedAssetFrames: [],
|
|
36
|
-
hydrationBindingState: createServerHydrationBindingState()
|
|
37
|
-
};
|
|
38
|
-
return renderStateStorage.run(
|
|
39
|
-
state,
|
|
40
|
-
() => withActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY, state, render)
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Runs work with the current page-level hydrate binding namespace attached to JSX SSR.
|
|
45
|
-
*
|
|
46
|
-
* This lets page, layout, and document renders participate in one client-owned
|
|
47
|
-
* hydration root even though Ecopages composes them through multiple sibling render calls.
|
|
48
|
-
*/
|
|
49
|
-
withHydrationBindingScope(render) {
|
|
50
|
-
return withServerHydrationBindingState(this.getState().hydrationBindingState, render);
|
|
51
|
-
}
|
|
52
|
-
beginCollectedAssetFrame() {
|
|
53
|
-
const state = this.getState();
|
|
54
|
-
const frame = [];
|
|
55
|
-
state.collectedAssetFrames.push(frame);
|
|
56
|
-
return frame;
|
|
57
|
-
}
|
|
58
|
-
endCollectedAssetFrame(frame) {
|
|
59
|
-
const activeFrame = this.getState().collectedAssetFrames.pop();
|
|
60
|
-
if (!activeFrame || activeFrame !== frame) {
|
|
61
|
-
return this.dedupeProcessedAssets(frame);
|
|
62
|
-
}
|
|
63
|
-
return this.dedupeProcessedAssets(activeFrame);
|
|
64
|
-
}
|
|
65
|
-
recordCollectedAssets(collectedAssets) {
|
|
66
|
-
const dedupedAssets = this.dedupeProcessedAssets(collectedAssets);
|
|
67
|
-
const state = this.getState();
|
|
68
|
-
const activeFrame = state.collectedAssetFrames[state.collectedAssetFrames.length - 1];
|
|
69
|
-
if (activeFrame) {
|
|
70
|
-
activeFrame.push(...dedupedAssets);
|
|
71
|
-
}
|
|
72
|
-
return dedupedAssets;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Resolves the current render-session state from the local async scope first.
|
|
76
|
-
*
|
|
77
|
-
* @remarks
|
|
78
|
-
* The fallback read from `@ecopages/jsx/server` keeps compatibility with code
|
|
79
|
-
* that still inspects the public SSR scope directly, while the async-local
|
|
80
|
-
* store remains the authoritative source for renderer-owned bookkeeping.
|
|
81
|
-
*/
|
|
82
|
-
getState() {
|
|
83
|
-
const state = renderStateStorage.getStore() ?? getActiveSsrScopeValue(ECOPAGES_JSX_SSR_RENDER_STATE_KEY);
|
|
84
|
-
if (!state) {
|
|
85
|
-
throw new Error("Ecopages JSX SSR render state is unavailable outside the active render scope.");
|
|
86
|
-
}
|
|
87
|
-
return state;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
export {
|
|
91
|
-
ECOPAGES_JSX_SSR_RENDER_STATE_KEY,
|
|
92
|
-
EcopagesJsxRenderSession
|
|
93
|
-
};
|