@ecopages/core 0.2.0-alpha.11 → 0.2.0-alpha.13
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 +7 -10
- package/README.md +5 -4
- package/package.json +30 -6
- package/src/adapters/bun/hmr-manager.js +2 -2
- package/src/adapters/node/node-hmr-manager.js +2 -2
- package/src/adapters/node/server-adapter.d.ts +2 -2
- package/src/adapters/node/server-adapter.js +5 -5
- package/src/build/build-adapter.d.ts +8 -6
- package/src/build/build-adapter.js +44 -7
- package/src/eco/eco.js +18 -118
- package/src/eco/eco.utils.d.ts +1 -40
- package/src/eco/eco.utils.js +5 -35
- package/src/hmr/hmr-strategy.d.ts +8 -6
- package/src/integrations/ghtml/ghtml-renderer.d.ts +6 -1
- package/src/integrations/ghtml/ghtml-renderer.js +29 -28
- package/src/plugins/foreign-jsx-override-plugin.d.ts +31 -0
- package/src/plugins/foreign-jsx-override-plugin.js +35 -0
- package/src/plugins/integration-plugin.d.ts +90 -29
- package/src/plugins/integration-plugin.js +62 -19
- package/src/route-renderer/GRAPH.md +54 -84
- package/src/route-renderer/README.md +11 -19
- package/src/route-renderer/orchestration/component-render-context.d.ts +83 -0
- package/src/route-renderer/orchestration/component-render-context.js +147 -0
- package/src/route-renderer/orchestration/integration-renderer.d.ts +219 -81
- package/src/route-renderer/orchestration/integration-renderer.js +415 -171
- package/src/route-renderer/orchestration/queued-boundary-runtime.service.d.ts +93 -0
- package/src/route-renderer/orchestration/queued-boundary-runtime.service.js +155 -0
- package/src/route-renderer/orchestration/render-execution.service.d.ts +8 -70
- package/src/route-renderer/orchestration/render-execution.service.js +28 -113
- package/src/route-renderer/orchestration/render-output.utils.d.ts +46 -0
- package/src/route-renderer/orchestration/render-output.utils.js +65 -0
- package/src/route-renderer/orchestration/render-preparation.service.d.ts +0 -6
- package/src/route-renderer/orchestration/render-preparation.service.js +5 -13
- package/src/route-renderer/orchestration/template-serialization.d.ts +38 -0
- package/src/route-renderer/orchestration/template-serialization.js +45 -0
- package/src/route-renderer/page-loading/dependency-resolver.js +10 -8
- package/src/router/client/navigation-coordinator.js +2 -2
- package/src/router/server/fs-router-scanner.js +6 -1
- package/src/services/module-loading/node-bootstrap-plugin.js +14 -1
- package/src/services/module-loading/page-module-import.service.js +1 -1
- package/src/services/runtime-state/dev-graph.service.d.ts +5 -5
- package/src/services/runtime-state/dev-graph.service.js +10 -10
- package/src/types/public-types.d.ts +18 -3
- package/src/utils/html-escaping.d.ts +7 -0
- package/src/utils/html-escaping.js +6 -0
- package/src/eco/component-render-context.d.ts +0 -105
- package/src/eco/component-render-context.js +0 -94
- package/src/route-renderer/component-graph/component-graph-executor.d.ts +0 -33
- package/src/route-renderer/component-graph/component-graph-executor.js +0 -30
- package/src/route-renderer/component-graph/component-graph.d.ts +0 -53
- package/src/route-renderer/component-graph/component-graph.js +0 -94
- package/src/route-renderer/component-graph/component-marker.d.ts +0 -52
- package/src/route-renderer/component-graph/component-marker.js +0 -46
- package/src/route-renderer/component-graph/component-reference.d.ts +0 -11
- package/src/route-renderer/component-graph/component-reference.js +0 -39
- package/src/route-renderer/component-graph/marker-graph-resolver.d.ts +0 -79
- package/src/route-renderer/component-graph/marker-graph-resolver.js +0 -117
|
@@ -5,9 +5,6 @@ import {
|
|
|
5
5
|
AssetFactory
|
|
6
6
|
} from "../../services/assets/asset-processing-service/index.js";
|
|
7
7
|
import { buildGlobalInjectorBootstrapContent, buildGlobalInjectorMapScript } from "../../eco/global-injector-map.js";
|
|
8
|
-
import {
|
|
9
|
-
runWithComponentRenderContext
|
|
10
|
-
} from "../../eco/component-render-context.js";
|
|
11
8
|
class RenderPreparationService {
|
|
12
9
|
appConfig;
|
|
13
10
|
assetProcessingService;
|
|
@@ -52,13 +49,13 @@ class RenderPreparationService {
|
|
|
52
49
|
const allDependencies = [...resolvedDependencies, ...usedIntegrationDependencies, ...pageDeps];
|
|
53
50
|
let componentRender;
|
|
54
51
|
if (callbacks.shouldRenderPageComponent({ Page, Layout, options: routeOptions })) {
|
|
55
|
-
|
|
56
|
-
currentIntegrationName,
|
|
52
|
+
const pageRootRender = await this.renderPageRoot({
|
|
57
53
|
Page,
|
|
58
54
|
props,
|
|
59
55
|
routeOptions,
|
|
60
56
|
callbacks
|
|
61
57
|
});
|
|
58
|
+
componentRender = pageRootRender.componentRender;
|
|
62
59
|
if (componentRender.assets?.length) {
|
|
63
60
|
allDependencies.push(...componentRender.assets);
|
|
64
61
|
}
|
|
@@ -196,12 +193,8 @@ class RenderPreparationService {
|
|
|
196
193
|
* @returns Structured component render result.
|
|
197
194
|
*/
|
|
198
195
|
async renderPageRoot(input) {
|
|
199
|
-
|
|
200
|
-
{
|
|
201
|
-
currentIntegration: input.currentIntegrationName,
|
|
202
|
-
boundaryContext: input.callbacks.getComponentRenderBoundaryContext()
|
|
203
|
-
},
|
|
204
|
-
async () => input.callbacks.renderPageComponent({
|
|
196
|
+
return {
|
|
197
|
+
componentRender: await input.callbacks.renderPageComponent({
|
|
205
198
|
component: input.Page,
|
|
206
199
|
props: {
|
|
207
200
|
...input.props,
|
|
@@ -209,8 +202,7 @@ class RenderPreparationService {
|
|
|
209
202
|
query: input.routeOptions.query || {}
|
|
210
203
|
}
|
|
211
204
|
})
|
|
212
|
-
|
|
213
|
-
return execution.value;
|
|
205
|
+
};
|
|
214
206
|
}
|
|
215
207
|
/**
|
|
216
208
|
* Builds the runtime assets needed to bootstrap global lazy trigger execution.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal template-result shape that core can serialize generically.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Core intentionally models only the transport shape used during deferred
|
|
6
|
+
* cross-integration child passing: alternating static string segments plus
|
|
7
|
+
* interpolated runtime values. Concrete runtime markers that identify a given
|
|
8
|
+
* framework's template payload must stay outside core and be registered by the
|
|
9
|
+
* owning integration package.
|
|
10
|
+
*/
|
|
11
|
+
export type SerializableTemplateShape = {
|
|
12
|
+
strings: readonly string[];
|
|
13
|
+
values?: readonly unknown[];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Integration-owned adapter that teaches core how to serialize one deferred
|
|
17
|
+
* template payload shape.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* The separation here is intentional: core owns the HTML serialization logic,
|
|
21
|
+
* but integrations own runtime-shape detection. That keeps package-specific
|
|
22
|
+
* markers such as framework template sentinels out of core while still letting
|
|
23
|
+
* deferred children round-trip through mixed renderer boundaries.
|
|
24
|
+
*/
|
|
25
|
+
export type DeferredTemplateSerializer<TTemplate extends SerializableTemplateShape = SerializableTemplateShape> = {
|
|
26
|
+
matches(value: unknown): value is TTemplate;
|
|
27
|
+
serialize(template: TTemplate, serializeValue: (value: unknown) => string | undefined): string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Serializes a generic template shape into HTML.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* This handles only HTML reconstruction semantics: quoted attribute values,
|
|
34
|
+
* boolean attributes, and omission of client-only event or property bindings.
|
|
35
|
+
* It does not decide whether an arbitrary value belongs to a framework-specific
|
|
36
|
+
* template runtime; integrations must make that decision before delegating here.
|
|
37
|
+
*/
|
|
38
|
+
export declare function serializeTemplateShape(template: SerializableTemplateShape, serializeValue: (value: unknown) => string | undefined): string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { escapeHtmlAttribute } from "../../utils/html-escaping.js";
|
|
2
|
+
const ATTRIBUTE_TAIL_PATTERN = /(\s+)([@.?!]?)([^\s"'<>/=`@.?!]+)=$/;
|
|
3
|
+
function getSerializableTemplateAttribute(stringPart) {
|
|
4
|
+
const match = ATTRIBUTE_TAIL_PATTERN.exec(stringPart);
|
|
5
|
+
if (!match) return void 0;
|
|
6
|
+
return {
|
|
7
|
+
leading: stringPart.slice(0, match.index),
|
|
8
|
+
whitespace: match[1],
|
|
9
|
+
prefix: match[2],
|
|
10
|
+
name: match[3]
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function serializeTemplateShape(template, serializeValue) {
|
|
14
|
+
const values = template.values ?? [];
|
|
15
|
+
let html = "";
|
|
16
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
17
|
+
const stringPart = template.strings[index] ?? "";
|
|
18
|
+
const serializedValue = serializeValue(values[index]);
|
|
19
|
+
const attribute = getSerializableTemplateAttribute(stringPart);
|
|
20
|
+
if (!attribute) {
|
|
21
|
+
html += stringPart;
|
|
22
|
+
html += serializedValue ?? "";
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
html += attribute.leading;
|
|
26
|
+
if (attribute.prefix === "@" || attribute.prefix === "!" || attribute.prefix === ".") {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (attribute.prefix === "?") {
|
|
30
|
+
if (serializedValue) {
|
|
31
|
+
html += `${attribute.whitespace}${attribute.name}`;
|
|
32
|
+
}
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (serializedValue === void 0) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
html += `${attribute.whitespace}${attribute.name}="${escapeHtmlAttribute(serializedValue)}"`;
|
|
39
|
+
}
|
|
40
|
+
html += template.strings[values.length] ?? "";
|
|
41
|
+
return html;
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
serializeTemplateShape
|
|
45
|
+
};
|
|
@@ -34,7 +34,12 @@ function extractEcopagesVirtualImports(file) {
|
|
|
34
34
|
const namedImports = [];
|
|
35
35
|
for (const spec of node.specifiers ?? []) {
|
|
36
36
|
if (spec.type === "ImportSpecifier") {
|
|
37
|
-
|
|
37
|
+
let importedName = spec.local?.name;
|
|
38
|
+
if (spec.imported?.type === "Identifier") {
|
|
39
|
+
importedName = spec.imported.name;
|
|
40
|
+
} else if (spec.imported?.type === "Literal") {
|
|
41
|
+
importedName = spec.imported.value;
|
|
42
|
+
}
|
|
38
43
|
namedImports.push(importedName);
|
|
39
44
|
}
|
|
40
45
|
}
|
|
@@ -62,7 +67,7 @@ function createModuleScriptName(from, imports) {
|
|
|
62
67
|
return `module-${hash}`;
|
|
63
68
|
}
|
|
64
69
|
function createNamedImportModuleSource(from, imports) {
|
|
65
|
-
const namedImports = imports.join(", ");
|
|
70
|
+
const namedImports = [...new Set(imports)].sort().join(", ");
|
|
66
71
|
return `export { ${namedImports} } from '${from}';`;
|
|
67
72
|
}
|
|
68
73
|
function createNamespaceImportModuleSource(from) {
|
|
@@ -259,14 +264,11 @@ class DependencyResolverService {
|
|
|
259
264
|
}
|
|
260
265
|
scriptDependencyKeys.add(depKey2);
|
|
261
266
|
dependencies.push(
|
|
262
|
-
AssetFactory.
|
|
267
|
+
AssetFactory.createInlineContentScript({
|
|
263
268
|
position: "head",
|
|
264
269
|
content,
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
defer: "",
|
|
268
|
-
...attributes
|
|
269
|
-
}
|
|
270
|
+
bundle: false,
|
|
271
|
+
attributes
|
|
270
272
|
})
|
|
271
273
|
);
|
|
272
274
|
continue;
|
|
@@ -16,7 +16,7 @@ function getCandidateOwners(currentOwner, registrations, excludedOwner) {
|
|
|
16
16
|
}
|
|
17
17
|
return owners;
|
|
18
18
|
}
|
|
19
|
-
function createEcoNavigationRuntime(
|
|
19
|
+
function createEcoNavigationRuntime() {
|
|
20
20
|
const registrations = /* @__PURE__ */ new Map();
|
|
21
21
|
const listeners = /* @__PURE__ */ new Set();
|
|
22
22
|
let owner = "none";
|
|
@@ -204,7 +204,7 @@ function getEcoNavigationRuntime(windowObject = window) {
|
|
|
204
204
|
const runtimeWindow = windowObject;
|
|
205
205
|
runtimeWindow.__ECO_PAGES__ = runtimeWindow.__ECO_PAGES__ || {};
|
|
206
206
|
if (!runtimeWindow.__ECO_PAGES__.navigation) {
|
|
207
|
-
runtimeWindow.__ECO_PAGES__.navigation = createEcoNavigationRuntime(
|
|
207
|
+
runtimeWindow.__ECO_PAGES__.navigation = createEcoNavigationRuntime();
|
|
208
208
|
}
|
|
209
209
|
return runtimeWindow.__ECO_PAGES__.navigation;
|
|
210
210
|
}
|
|
@@ -113,7 +113,12 @@ class FSRouterScanner {
|
|
|
113
113
|
const filePath = path.join(this.dir, file);
|
|
114
114
|
const isCatchAll = filePath.includes("[...");
|
|
115
115
|
const isDynamic = !isCatchAll && filePath.includes("[") && filePath.includes("]");
|
|
116
|
-
|
|
116
|
+
let kind = "exact";
|
|
117
|
+
if (isCatchAll) {
|
|
118
|
+
kind = "catch-all";
|
|
119
|
+
} else if (isDynamic) {
|
|
120
|
+
kind = "dynamic";
|
|
121
|
+
}
|
|
117
122
|
return { route, routePath, filePath, kind };
|
|
118
123
|
}
|
|
119
124
|
async scan() {
|
|
@@ -104,7 +104,20 @@ function resolveNodeBootstrapDependency(args, options) {
|
|
|
104
104
|
try {
|
|
105
105
|
resolvedPath2 = resolveFromCore(args.path);
|
|
106
106
|
} catch {
|
|
107
|
-
|
|
107
|
+
try {
|
|
108
|
+
resolvedPath2 = resolveSpecifier(args.path, resolveParent);
|
|
109
|
+
} catch {
|
|
110
|
+
const packageName = getPackageNameFromSpecifier(args.path);
|
|
111
|
+
const candidatePath = path.join(options.projectDir, "node_modules", packageName);
|
|
112
|
+
const candidatePackageJson = path.join(candidatePath, "package.json");
|
|
113
|
+
if (existsSync(candidatePackageJson)) {
|
|
114
|
+
ensureRuntimePackageLink(options.runtimeNodeModulesDir, args.path, candidatePackageJson);
|
|
115
|
+
return { path: args.path, external: true };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!resolvedPath2) {
|
|
120
|
+
return void 0;
|
|
108
121
|
}
|
|
109
122
|
if (resolvedPath2.includes(`${path.sep}node_modules${path.sep}`)) {
|
|
110
123
|
ensureRuntimePackageLink(options.runtimeNodeModulesDir, args.path, resolvedPath2);
|
|
@@ -32,7 +32,7 @@ export declare class NoopDevGraphService implements DevGraphService {
|
|
|
32
32
|
/**
|
|
33
33
|
* Invalidates all server-side module state by incrementing the shared version.
|
|
34
34
|
*/
|
|
35
|
-
invalidateServerModules(
|
|
35
|
+
invalidateServerModules(changedFiles?: string[]): void;
|
|
36
36
|
/**
|
|
37
37
|
* Indicates that this graph cannot target invalidation to one entrypoint set.
|
|
38
38
|
*/
|
|
@@ -41,19 +41,19 @@ export declare class NoopDevGraphService implements DevGraphService {
|
|
|
41
41
|
* Returns an empty entrypoint set because this implementation stores no
|
|
42
42
|
* dependency graph metadata.
|
|
43
43
|
*/
|
|
44
|
-
getDependencyEntrypoints(
|
|
44
|
+
getDependencyEntrypoints(filePath: string): Set<string>;
|
|
45
45
|
/**
|
|
46
46
|
* Accepts dependency updates to preserve interface compatibility, but stores no
|
|
47
47
|
* graph state in the noop implementation.
|
|
48
48
|
*/
|
|
49
|
-
setEntrypointDependencies(
|
|
49
|
+
setEntrypointDependencies(entrypointPath: string, dependencies: string[]): void;
|
|
50
50
|
/**
|
|
51
51
|
* Clears one entrypoint from the graph.
|
|
52
52
|
*
|
|
53
53
|
* @remarks
|
|
54
54
|
* There is no stored graph state in this implementation, so this is a no-op.
|
|
55
55
|
*/
|
|
56
|
-
clearEntrypointDependencies(
|
|
56
|
+
clearEntrypointDependencies(entrypointPath: string): void;
|
|
57
57
|
/**
|
|
58
58
|
* Resets graph-owned state for a fresh runtime cycle.
|
|
59
59
|
*/
|
|
@@ -82,7 +82,7 @@ export declare class InMemoryDevGraphService implements DevGraphService {
|
|
|
82
82
|
* modules. Selective dependency lookups are used by callers that need to limit
|
|
83
83
|
* browser rebuild work.
|
|
84
84
|
*/
|
|
85
|
-
invalidateServerModules(
|
|
85
|
+
invalidateServerModules(changedFiles?: string[]): void;
|
|
86
86
|
/**
|
|
87
87
|
* Indicates that this graph can answer dependency-to-entrypoint lookups.
|
|
88
88
|
*/
|
|
@@ -21,8 +21,8 @@ class NoopDevGraphService {
|
|
|
21
21
|
/**
|
|
22
22
|
* Invalidates all server-side module state by incrementing the shared version.
|
|
23
23
|
*/
|
|
24
|
-
invalidateServerModules(
|
|
25
|
-
this.invalidationState.invalidateServerModules(
|
|
24
|
+
invalidateServerModules(changedFiles) {
|
|
25
|
+
this.invalidationState.invalidateServerModules(changedFiles);
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Indicates that this graph cannot target invalidation to one entrypoint set.
|
|
@@ -34,15 +34,15 @@ class NoopDevGraphService {
|
|
|
34
34
|
* Returns an empty entrypoint set because this implementation stores no
|
|
35
35
|
* dependency graph metadata.
|
|
36
36
|
*/
|
|
37
|
-
getDependencyEntrypoints(
|
|
38
|
-
return this.dependencyGraph.getDependencyEntrypoints(
|
|
37
|
+
getDependencyEntrypoints(filePath) {
|
|
38
|
+
return this.dependencyGraph.getDependencyEntrypoints(filePath);
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* Accepts dependency updates to preserve interface compatibility, but stores no
|
|
42
42
|
* graph state in the noop implementation.
|
|
43
43
|
*/
|
|
44
|
-
setEntrypointDependencies(
|
|
45
|
-
this.dependencyGraph.setEntrypointDependencies(
|
|
44
|
+
setEntrypointDependencies(entrypointPath, dependencies) {
|
|
45
|
+
this.dependencyGraph.setEntrypointDependencies(entrypointPath, dependencies);
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* Clears one entrypoint from the graph.
|
|
@@ -50,8 +50,8 @@ class NoopDevGraphService {
|
|
|
50
50
|
* @remarks
|
|
51
51
|
* There is no stored graph state in this implementation, so this is a no-op.
|
|
52
52
|
*/
|
|
53
|
-
clearEntrypointDependencies(
|
|
54
|
-
this.dependencyGraph.clearEntrypointDependencies(
|
|
53
|
+
clearEntrypointDependencies(entrypointPath) {
|
|
54
|
+
this.dependencyGraph.clearEntrypointDependencies(entrypointPath);
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
57
|
* Resets graph-owned state for a fresh runtime cycle.
|
|
@@ -78,8 +78,8 @@ class InMemoryDevGraphService {
|
|
|
78
78
|
* modules. Selective dependency lookups are used by callers that need to limit
|
|
79
79
|
* browser rebuild work.
|
|
80
80
|
*/
|
|
81
|
-
invalidateServerModules(
|
|
82
|
-
this.invalidationState.invalidateServerModules(
|
|
81
|
+
invalidateServerModules(changedFiles) {
|
|
82
|
+
this.invalidationState.invalidateServerModules(changedFiles);
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
85
85
|
* Indicates that this graph can answer dependency-to-entrypoint lookups.
|
|
@@ -2,6 +2,7 @@ import type { Readable } from 'node:stream';
|
|
|
2
2
|
import type { ApiResponseBuilder } from '../adapters/shared/api-response.js';
|
|
3
3
|
import type { BuildExecutor } from '../build/build-adapter.js';
|
|
4
4
|
import type { EcoBuildPlugin } from '../build/build-types.js';
|
|
5
|
+
import type { ComponentBoundaryRuntime } from '../route-renderer/orchestration/component-render-context.js';
|
|
5
6
|
import type { EcoPageComponent } from '../eco/eco.types.js';
|
|
6
7
|
import type { EcoPagesAppConfig } from './internal-types.js';
|
|
7
8
|
import type { HmrStrategy } from '../hmr/hmr-strategy.js';
|
|
@@ -11,10 +12,9 @@ import type { CacheStats, CacheStrategy } from '../services/cache/cache.types.js
|
|
|
11
12
|
import type { InteractionEventsString as ScriptsInjectorInteractionEventsString } from '@ecopages/scripts-injector/types';
|
|
12
13
|
export type { EcoPagesAppConfig } from './internal-types.js';
|
|
13
14
|
export type { EcoPageComponent } from '../eco/eco.types.js';
|
|
14
|
-
export type { MarkerGraphContext } from '../route-renderer/component-graph/marker-graph-resolver.js';
|
|
15
15
|
export type { ProcessedAsset } from '../services/assets/asset-processing-service/assets.types.js';
|
|
16
16
|
import type { StandardSchema, StandardSchemaResult, StandardSchemaSuccessResult, StandardSchemaFailureResult, StandardSchemaIssue, InferOutput } from '../services/validation/standard-schema.types.js';
|
|
17
|
-
export type { StandardSchema, StandardSchemaResult, StandardSchemaSuccessResult, StandardSchemaFailureResult, StandardSchemaIssue, InferOutput, };
|
|
17
|
+
export type { StandardSchema, StandardSchemaResult, StandardSchemaSuccessResult, StandardSchemaFailureResult, StandardSchemaIssue, InferOutput, ComponentBoundaryRuntime, };
|
|
18
18
|
export type InteractionEventsString = ScriptsInjectorInteractionEventsString;
|
|
19
19
|
export type DependencyLazyTrigger = {
|
|
20
20
|
'on:idle': true;
|
|
@@ -262,6 +262,21 @@ export type EcoComponentDependencies = {
|
|
|
262
262
|
components?: EcoComponent[];
|
|
263
263
|
};
|
|
264
264
|
export type EcoPagesElement = string | Promise<string>;
|
|
265
|
+
/**
|
|
266
|
+
* Serializable child payloads accepted by cross-integration deferred rendering.
|
|
267
|
+
*
|
|
268
|
+
* This models the broad value shapes that EcoPages already flattens when a
|
|
269
|
+
* foreign component boundary serializes its children for another integration.
|
|
270
|
+
* It is intentionally transport-oriented rather than framework-native, so it
|
|
271
|
+
* can be shared across Kita, Lit, React, and Ecopages JSX authoring surfaces
|
|
272
|
+
* without coupling core types to any one renderer.
|
|
273
|
+
*/
|
|
274
|
+
export type EcoChildren = string | Promise<string> | number | bigint | boolean | null | undefined | readonly EcoChildren[] | {
|
|
275
|
+
strings: readonly string[];
|
|
276
|
+
values?: readonly EcoChildren[];
|
|
277
|
+
} | {
|
|
278
|
+
[key: string]: EcoChildren;
|
|
279
|
+
};
|
|
265
280
|
/**
|
|
266
281
|
* Represents the input configuration for EcoPages.
|
|
267
282
|
*/
|
|
@@ -667,7 +682,7 @@ export type IntegrationRendererRenderOptions<C = EcoPagesElement> = RouteRendere
|
|
|
667
682
|
export interface ComponentRenderInput {
|
|
668
683
|
component: EcoComponent;
|
|
669
684
|
props: Record<string, unknown>;
|
|
670
|
-
children?:
|
|
685
|
+
children?: unknown;
|
|
671
686
|
integrationContext?: unknown;
|
|
672
687
|
}
|
|
673
688
|
export interface ComponentRenderResult {
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import type { EcoComponent } from '../types/public-types.js';
|
|
2
|
-
import type { MarkerNodeId } from '../route-renderer/component-graph/component-marker.js';
|
|
3
|
-
/**
|
|
4
|
-
* Outcome returned by boundary policy during one component render pass.
|
|
5
|
-
*
|
|
6
|
-
* - `inline`: render the target component immediately in the current pass
|
|
7
|
-
* - `defer`: emit an `eco-marker` and resolve it during the marker graph phase
|
|
8
|
-
*/
|
|
9
|
-
export type BoundaryRenderMode = 'inline' | 'defer';
|
|
10
|
-
/**
|
|
11
|
-
* Input provided to boundary policy when a component boundary is reached.
|
|
12
|
-
*
|
|
13
|
-
* This keeps `eco.component()` decoupled from concrete integration/plugin
|
|
14
|
-
* objects while still giving policy enough information to decide whether the
|
|
15
|
-
* boundary should render immediately or be deferred.
|
|
16
|
-
*/
|
|
17
|
-
export type BoundaryRenderDecisionInput = {
|
|
18
|
-
currentIntegration: string;
|
|
19
|
-
targetIntegration?: string;
|
|
20
|
-
component: EcoComponent;
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* Narrow render-pass facade used by `eco.component()` for boundary decisions.
|
|
24
|
-
*
|
|
25
|
-
* The boundary context is intentionally small so component rendering can remain
|
|
26
|
-
* unaware of integration registries, plugin instances, or renderer lifecycles.
|
|
27
|
-
*/
|
|
28
|
-
export type ComponentRenderBoundaryContext = {
|
|
29
|
-
/**
|
|
30
|
-
* Decides whether the next component boundary should render inline or defer to
|
|
31
|
-
* the marker graph stage.
|
|
32
|
-
*
|
|
33
|
-
* @param input Boundary metadata for the current render pass.
|
|
34
|
-
* @returns Boundary rendering mode for the target component.
|
|
35
|
-
*/
|
|
36
|
-
decideBoundaryRender(input: BoundaryRenderDecisionInput): BoundaryRenderMode;
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* Per-render mutable state used while collecting marker graph references.
|
|
40
|
-
*
|
|
41
|
-
* Counters generate deterministic ids within one render execution.
|
|
42
|
-
*/
|
|
43
|
-
type ComponentRenderContext = {
|
|
44
|
-
currentIntegration: string;
|
|
45
|
-
boundaryContext: ComponentRenderBoundaryContext;
|
|
46
|
-
nextNodeId: number;
|
|
47
|
-
nextPropsRefId: number;
|
|
48
|
-
nextSlotRefId: number;
|
|
49
|
-
propsByRef: Record<string, Record<string, unknown>>;
|
|
50
|
-
slotChildrenByRef: Record<string, MarkerNodeId[]>;
|
|
51
|
-
};
|
|
52
|
-
/**
|
|
53
|
-
* Serializable graph context captured from one render execution.
|
|
54
|
-
*
|
|
55
|
-
* This payload is merged with explicit page-module graph context before marker
|
|
56
|
-
* resolution in the route renderer.
|
|
57
|
-
*/
|
|
58
|
-
export type ComponentGraphContext = {
|
|
59
|
-
propsByRef: Record<string, Record<string, unknown>>;
|
|
60
|
-
slotChildrenByRef: Record<string, MarkerNodeId[]>;
|
|
61
|
-
};
|
|
62
|
-
/**
|
|
63
|
-
* Returns the current component render context, if one is active.
|
|
64
|
-
*
|
|
65
|
-
* @returns Active render context or `undefined` outside render execution.
|
|
66
|
-
*/
|
|
67
|
-
export declare function getComponentRenderContext(): ComponentRenderContext | undefined;
|
|
68
|
-
/**
|
|
69
|
-
* Allocates the next marker node id in the active render context.
|
|
70
|
-
*
|
|
71
|
-
* @param context Active render context.
|
|
72
|
-
* @returns Stable marker node id for this render pass.
|
|
73
|
-
*/
|
|
74
|
-
export declare function createNodeId(context: ComponentRenderContext): MarkerNodeId;
|
|
75
|
-
/**
|
|
76
|
-
* Allocates the next props reference id in the active render context.
|
|
77
|
-
*
|
|
78
|
-
* @param context Active render context.
|
|
79
|
-
* @returns Props reference key.
|
|
80
|
-
*/
|
|
81
|
-
export declare function createPropsRef(context: ComponentRenderContext): string;
|
|
82
|
-
/**
|
|
83
|
-
* Allocates the next slot reference id in the active render context.
|
|
84
|
-
*
|
|
85
|
-
* @param context Active render context.
|
|
86
|
-
* @returns Slot reference key.
|
|
87
|
-
*/
|
|
88
|
-
export declare function createSlotRef(context: ComponentRenderContext): string;
|
|
89
|
-
/**
|
|
90
|
-
* Runs render work under a fresh component render context and returns both:
|
|
91
|
-
* - the render result value
|
|
92
|
-
* - captured graph reference maps for downstream marker resolution
|
|
93
|
-
*
|
|
94
|
-
* @param input Execution metadata for current integration and boundary policy.
|
|
95
|
-
* @param render Async render function to execute inside the context.
|
|
96
|
-
* @returns Render result and captured graph context maps.
|
|
97
|
-
*/
|
|
98
|
-
export declare function runWithComponentRenderContext<T>(input: {
|
|
99
|
-
currentIntegration: string;
|
|
100
|
-
boundaryContext: ComponentRenderBoundaryContext;
|
|
101
|
-
}, render: () => Promise<T>): Promise<{
|
|
102
|
-
value: T;
|
|
103
|
-
graphContext: ComponentGraphContext;
|
|
104
|
-
}>;
|
|
105
|
-
export {};
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
const GLOBAL_COMPONENT_RENDER_CONTEXT_STATE_KEY = "__ECOPAGES_COMPONENT_RENDER_CONTEXT_STATE__";
|
|
2
|
-
function getSharedContextScope() {
|
|
3
|
-
const globalProcess = globalThis.process;
|
|
4
|
-
if (globalProcess && typeof globalProcess === "object") {
|
|
5
|
-
return globalProcess;
|
|
6
|
-
}
|
|
7
|
-
return globalThis;
|
|
8
|
-
}
|
|
9
|
-
function getComponentRenderContextState() {
|
|
10
|
-
const sharedScope = getSharedContextScope();
|
|
11
|
-
sharedScope[GLOBAL_COMPONENT_RENDER_CONTEXT_STATE_KEY] ??= {
|
|
12
|
-
contextStack: [],
|
|
13
|
-
nodeContextStorage: null,
|
|
14
|
-
nodeContextStorageLoader: null
|
|
15
|
-
};
|
|
16
|
-
return sharedScope[GLOBAL_COMPONENT_RENDER_CONTEXT_STATE_KEY];
|
|
17
|
-
}
|
|
18
|
-
async function getContextStorage() {
|
|
19
|
-
const state = getComponentRenderContextState();
|
|
20
|
-
if (state.nodeContextStorage) {
|
|
21
|
-
return state.nodeContextStorage;
|
|
22
|
-
}
|
|
23
|
-
if (state.nodeContextStorageLoader) {
|
|
24
|
-
return state.nodeContextStorageLoader;
|
|
25
|
-
}
|
|
26
|
-
state.nodeContextStorageLoader = import("node:async_hooks").then((module) => {
|
|
27
|
-
const storage = new module.AsyncLocalStorage();
|
|
28
|
-
state.nodeContextStorage = {
|
|
29
|
-
getStore: () => storage.getStore(),
|
|
30
|
-
run: (store, callback) => storage.run(store, callback)
|
|
31
|
-
};
|
|
32
|
-
return state.nodeContextStorage;
|
|
33
|
-
}).catch(() => {
|
|
34
|
-
state.nodeContextStorage = null;
|
|
35
|
-
return null;
|
|
36
|
-
}).finally(() => {
|
|
37
|
-
state.nodeContextStorageLoader = null;
|
|
38
|
-
});
|
|
39
|
-
return state.nodeContextStorageLoader;
|
|
40
|
-
}
|
|
41
|
-
function getComponentRenderContext() {
|
|
42
|
-
const state = getComponentRenderContextState();
|
|
43
|
-
return state.nodeContextStorage?.getStore() ?? state.contextStack[state.contextStack.length - 1];
|
|
44
|
-
}
|
|
45
|
-
function createNodeId(context) {
|
|
46
|
-
context.nextNodeId += 1;
|
|
47
|
-
return `n_${context.nextNodeId}`;
|
|
48
|
-
}
|
|
49
|
-
function createPropsRef(context) {
|
|
50
|
-
context.nextPropsRefId += 1;
|
|
51
|
-
return `p_${context.nextPropsRefId}`;
|
|
52
|
-
}
|
|
53
|
-
function createSlotRef(context) {
|
|
54
|
-
context.nextSlotRefId += 1;
|
|
55
|
-
return `s_${context.nextSlotRefId}`;
|
|
56
|
-
}
|
|
57
|
-
async function runWithComponentRenderContext(input, render) {
|
|
58
|
-
const context = {
|
|
59
|
-
currentIntegration: input.currentIntegration,
|
|
60
|
-
boundaryContext: input.boundaryContext,
|
|
61
|
-
nextNodeId: 0,
|
|
62
|
-
nextPropsRefId: 0,
|
|
63
|
-
nextSlotRefId: 0,
|
|
64
|
-
propsByRef: {},
|
|
65
|
-
slotChildrenByRef: {}
|
|
66
|
-
};
|
|
67
|
-
const storage = await getContextStorage();
|
|
68
|
-
let value;
|
|
69
|
-
if (storage) {
|
|
70
|
-
value = await storage.run(context, render);
|
|
71
|
-
} else {
|
|
72
|
-
const state = getComponentRenderContextState();
|
|
73
|
-
state.contextStack.push(context);
|
|
74
|
-
try {
|
|
75
|
-
value = await render();
|
|
76
|
-
} finally {
|
|
77
|
-
state.contextStack.pop();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
value,
|
|
82
|
-
graphContext: {
|
|
83
|
-
propsByRef: context.propsByRef,
|
|
84
|
-
slotChildrenByRef: context.slotChildrenByRef
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
export {
|
|
89
|
-
createNodeId,
|
|
90
|
-
createPropsRef,
|
|
91
|
-
createSlotRef,
|
|
92
|
-
getComponentRenderContext,
|
|
93
|
-
runWithComponentRenderContext
|
|
94
|
-
};
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { ComponentMarker } from './component-marker.js';
|
|
2
|
-
import type { ComponentGraph } from './component-graph.js';
|
|
3
|
-
/**
|
|
4
|
-
* Render result returned by a graph node resolver.
|
|
5
|
-
*
|
|
6
|
-
* `html` is inserted in place of the corresponding marker token.
|
|
7
|
-
*/
|
|
8
|
-
export type GraphNodeRenderResult = {
|
|
9
|
-
html: string;
|
|
10
|
-
};
|
|
11
|
-
/**
|
|
12
|
-
* Callback used to render one marker node during graph execution.
|
|
13
|
-
*
|
|
14
|
-
* Resolver implementations may call integration-specific `renderComponent`
|
|
15
|
-
* and can use closure state to wire child output into parent render calls.
|
|
16
|
-
*/
|
|
17
|
-
export type GraphNodeResolver = (marker: ComponentMarker) => Promise<GraphNodeRenderResult>;
|
|
18
|
-
/**
|
|
19
|
-
* Resolves all markers in bottom-up order based on computed graph levels.
|
|
20
|
-
*
|
|
21
|
-
* Child nodes are resolved first so parent slot content can consume already
|
|
22
|
-
* resolved child HTML in subsequent resolver invocations.
|
|
23
|
-
*
|
|
24
|
-
* Nodes discovered from serialized props may not exist as literal marker tokens in
|
|
25
|
-
* `inputHtml`. They still resolve so parent nodes can consume their stitched child
|
|
26
|
-
* HTML, while replacement in the outer HTML buffer remains a no-op.
|
|
27
|
-
*
|
|
28
|
-
* @param inputHtml HTML containing marker tokens.
|
|
29
|
-
* @param graph Precomputed marker graph with topological levels.
|
|
30
|
-
* @param resolver Async callback that renders each marker node.
|
|
31
|
-
* @returns HTML with resolved markers replaced.
|
|
32
|
-
*/
|
|
33
|
-
export declare function resolveComponentGraph(inputHtml: string, graph: ComponentGraph, resolver: GraphNodeResolver): Promise<string>;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
function escapeRegex(value) {
|
|
2
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3
|
-
}
|
|
4
|
-
function replaceMarkerByNodeId(html, nodeId, replacement) {
|
|
5
|
-
const pattern = `<eco-marker[^>]*data-eco-node-id="${escapeRegex(nodeId)}"[^>]*><\\/eco-marker>`;
|
|
6
|
-
const markerRegex = new RegExp(pattern);
|
|
7
|
-
return html.replace(markerRegex, replacement);
|
|
8
|
-
}
|
|
9
|
-
async function resolveComponentGraph(inputHtml, graph, resolver) {
|
|
10
|
-
let html = inputHtml;
|
|
11
|
-
const markersById = /* @__PURE__ */ new Map();
|
|
12
|
-
for (const node of graph.nodes.values()) {
|
|
13
|
-
markersById.set(node.nodeId, node);
|
|
14
|
-
}
|
|
15
|
-
const levels = [...graph.levels].reverse();
|
|
16
|
-
for (const level of levels) {
|
|
17
|
-
for (const nodeId of level) {
|
|
18
|
-
const marker = markersById.get(nodeId);
|
|
19
|
-
if (!marker) {
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
const result = await resolver(marker);
|
|
23
|
-
html = replaceMarkerByNodeId(html, nodeId, result.html);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return html;
|
|
27
|
-
}
|
|
28
|
-
export {
|
|
29
|
-
resolveComponentGraph
|
|
30
|
-
};
|