@ecopages/core 0.2.0-alpha.37 → 0.2.0-alpha.39
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/package.json +2 -2
- package/src/eco/eco.browser.js +5 -0
- package/src/eco/eco.js +21 -0
- package/src/eco/eco.types.d.ts +55 -0
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.d.ts +35 -0
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +48 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/core",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.39",
|
|
4
4
|
"description": "Core package for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"directory": "packages/core"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@ecopages/file-system": "0.2.0-alpha.
|
|
20
|
+
"@ecopages/file-system": "0.2.0-alpha.39",
|
|
21
21
|
"@ecopages/logger": "^0.2.3",
|
|
22
22
|
"@ecopages/scripts-injector": "^0.1.5",
|
|
23
23
|
"@worker-tools/html-rewriter": "0.1.0-pre.19",
|
package/src/eco/eco.browser.js
CHANGED
|
@@ -10,6 +10,10 @@ function createComponentFactory(options) {
|
|
|
10
10
|
function component(options) {
|
|
11
11
|
return createComponentFactory(options);
|
|
12
12
|
}
|
|
13
|
+
function embed(component2, props, children) {
|
|
14
|
+
const nextProps = children === void 0 ? props : { ...props, children };
|
|
15
|
+
return component2(nextProps);
|
|
16
|
+
}
|
|
13
17
|
function html(options) {
|
|
14
18
|
return createComponentFactory(options);
|
|
15
19
|
}
|
|
@@ -71,6 +75,7 @@ function staticProps(fn) {
|
|
|
71
75
|
}
|
|
72
76
|
const eco = {
|
|
73
77
|
component,
|
|
78
|
+
embed,
|
|
74
79
|
html,
|
|
75
80
|
layout,
|
|
76
81
|
page,
|
package/src/eco/eco.js
CHANGED
|
@@ -46,6 +46,26 @@ function createComponentFactory(options) {
|
|
|
46
46
|
function component(options) {
|
|
47
47
|
return createComponentFactory(options);
|
|
48
48
|
}
|
|
49
|
+
function isMissingForeignChildInterceptionError(error) {
|
|
50
|
+
return error instanceof Error && error.message.startsWith("[ecopages] Missing foreign-child interception from ");
|
|
51
|
+
}
|
|
52
|
+
function embed(component2, props, children) {
|
|
53
|
+
const activeRenderContext = getComponentRenderContext();
|
|
54
|
+
const ecoComponent = component2;
|
|
55
|
+
const targetIntegration = ecoComponent.config?.integration ?? ecoComponent.config?.__eco?.integration;
|
|
56
|
+
const componentFile = ecoComponent.config?.__eco?.file ?? "unknown component";
|
|
57
|
+
const nextProps = children === void 0 ? props : { ...props, children };
|
|
58
|
+
try {
|
|
59
|
+
return component2(nextProps);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!isMissingForeignChildInterceptionError(error)) {
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
throw new Error(
|
|
65
|
+
`[ecopages] eco.embed() could not hand off the Foreign Child from ${activeRenderContext?.currentIntegration ?? "unknown integration"} to ${targetIntegration ?? "unknown integration"} for ${componentFile}. The active Integration renderer exposed a foreign-child runtime, but it did not intercept this cross-integration render. Ensure mixed-integration Page, Layout, Html, or Component renders install foreign-child handoff before calling eco.embed().`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
49
69
|
function html(options) {
|
|
50
70
|
return createComponentFactory(options);
|
|
51
71
|
}
|
|
@@ -86,6 +106,7 @@ function staticProps(fn) {
|
|
|
86
106
|
}
|
|
87
107
|
const eco = {
|
|
88
108
|
component,
|
|
109
|
+
embed,
|
|
89
110
|
html,
|
|
90
111
|
layout,
|
|
91
112
|
page,
|
package/src/eco/eco.types.d.ts
CHANGED
|
@@ -4,6 +4,51 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { DependencyLazyTrigger, EcoComponent, EcoComponentDependencies, EcoHtmlComponent, EcoInjectedMeta, EcoLayoutComponent, EcoPageLayoutComponent, EcoPagesElement, FileRouteMiddleware, GetMetadata, GetStaticPaths, GetStaticProps, HtmlTemplateProps, LayoutProps, RequestLocals, RequestPageContext } from '../types/public-types.js';
|
|
6
6
|
import type { CacheStrategy } from '../services/cache/cache.types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Extracts the props type from one eco component.
|
|
9
|
+
*/
|
|
10
|
+
export type PropsOf<TComponent extends EcoComponent> = TComponent extends EcoComponent<infer P, any> ? P : never;
|
|
11
|
+
/**
|
|
12
|
+
* Extracts the render result type from one eco component.
|
|
13
|
+
*/
|
|
14
|
+
export type ResultOf<TComponent extends EcoComponent> = TComponent extends EcoComponent<any, infer R> ? R : never;
|
|
15
|
+
/**
|
|
16
|
+
* Narrows an eco component to its callable function signature.
|
|
17
|
+
*
|
|
18
|
+
* This is useful for helper modules such as `EcoEmbed` that invoke a component
|
|
19
|
+
* directly and need to exclude the non-callable metadata shape from the public
|
|
20
|
+
* type surface.
|
|
21
|
+
*/
|
|
22
|
+
export type CallableComponentOf<TComponent extends EcoComponent> = Extract<TComponent, (props: any, ...args: any[]) => any>;
|
|
23
|
+
/**
|
|
24
|
+
* Extracts the declared `children` type from one eco component when present.
|
|
25
|
+
*/
|
|
26
|
+
export type ChildrenOf<TComponent extends EcoComponent> = PropsOf<TComponent> extends {
|
|
27
|
+
children?: infer T;
|
|
28
|
+
} ? T : PropsOf<TComponent> extends {
|
|
29
|
+
children: infer T;
|
|
30
|
+
} ? T : never;
|
|
31
|
+
/**
|
|
32
|
+
* Extracts the props accepted by `eco.embed()` before optional `children`
|
|
33
|
+
* injection.
|
|
34
|
+
*
|
|
35
|
+
* When the target component already declares `children`, callers pass the rest
|
|
36
|
+
* of the props bag here and provide `children` as the third `eco.embed()`
|
|
37
|
+
* argument or the `EcoEmbed` wrapper children slot.
|
|
38
|
+
*/
|
|
39
|
+
export type EmbedPropsOf<TComponent extends EcoComponent> = 'children' extends keyof PropsOf<TComponent> ? Omit<PropsOf<TComponent>, 'children'> : PropsOf<TComponent>;
|
|
40
|
+
/**
|
|
41
|
+
* Props accepted by integration-owned `EcoEmbed` adapters.
|
|
42
|
+
*
|
|
43
|
+
* The `component` field is narrowed to the callable portion of the target eco
|
|
44
|
+
* component so JSX wrappers can invoke `eco.embed()` without repeating local
|
|
45
|
+
* callable-component extraction logic.
|
|
46
|
+
*/
|
|
47
|
+
export type EcoEmbedProps<TComponent extends EcoComponent> = {
|
|
48
|
+
component: CallableComponentOf<TComponent>;
|
|
49
|
+
props: EmbedPropsOf<TComponent>;
|
|
50
|
+
children?: unknown;
|
|
51
|
+
};
|
|
7
52
|
type WithRequiredLocals<K extends keyof RequestLocals> = Omit<RequestLocals, K> & {
|
|
8
53
|
[P in K]-?: Exclude<RequestLocals[P], null | undefined>;
|
|
9
54
|
};
|
|
@@ -139,6 +184,16 @@ export interface Eco {
|
|
|
139
184
|
* @template E - Element/return type (EcoPagesElement for Kita, ReactNode for React)
|
|
140
185
|
*/
|
|
141
186
|
component: <P = {}, E = EcoPagesElement>(options: ComponentOptions<P, E>) => EcoComponent<P, E>;
|
|
187
|
+
/**
|
|
188
|
+
* Render a component explicitly, with an optional `children` argument.
|
|
189
|
+
*
|
|
190
|
+
* This is useful when authoring crosses integration boundaries and inline JSX
|
|
191
|
+
* would otherwise force one file to model multiple JSX namespaces.
|
|
192
|
+
*/
|
|
193
|
+
embed: {
|
|
194
|
+
<TComponent extends EcoComponent>(component: CallableComponentOf<TComponent>, props: PropsOf<TComponent>): ResultOf<TComponent>;
|
|
195
|
+
<TComponent extends EcoComponent>(component: CallableComponentOf<TComponent>, props: EmbedPropsOf<TComponent>, children: ChildrenOf<TComponent> | unknown): ResultOf<TComponent>;
|
|
196
|
+
};
|
|
142
197
|
/**
|
|
143
198
|
* Create a document shell component for the HTML wrapper.
|
|
144
199
|
*/
|
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
import type { NodeModuleScriptAsset } from '../../assets.types.js';
|
|
2
2
|
import { BaseScriptProcessor } from '../base/base-script-processor.js';
|
|
3
|
+
/**
|
|
4
|
+
* Processes browser script assets whose entrypoint is referenced by package specifier.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Resolution stays app-boundary-first: prefer the active build adapter, then fall back
|
|
8
|
+
* to ESM export-map resolution, and only then probe a small set of literal file paths.
|
|
9
|
+
*/
|
|
3
10
|
export declare class NodeModuleScriptProcessor extends BaseScriptProcessor<NodeModuleScriptAsset> {
|
|
4
11
|
process(dep: NodeModuleScriptAsset): Promise<import("../../assets.types.js").ProcessedAsset>;
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a node-module script entry from the current app boundary.
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* The build adapter remains the primary resolution surface because Bun/native
|
|
17
|
+
* host-owned builds may have a more accurate view of aliases and package
|
|
18
|
+
* ownership than core does. The local fallback only runs when that adapter
|
|
19
|
+
* resolution is unavailable.
|
|
20
|
+
*/
|
|
5
21
|
private resolveModulePath;
|
|
22
|
+
/**
|
|
23
|
+
* Resolves browser-owned script specifiers without relying on CommonJS resolution.
|
|
24
|
+
*
|
|
25
|
+
* @remarks
|
|
26
|
+
* This path intentionally stays ESM-first because these assets are emitted as
|
|
27
|
+
* browser scripts. We first ask Node's ESM resolver to evaluate the package
|
|
28
|
+
* export map from the app boundary. If that still fails, we fall back to a
|
|
29
|
+
* bounded filesystem probe so direct file installs and package subpaths like
|
|
30
|
+
* `pkg/client/entry` still resolve when the export map does not cover them.
|
|
31
|
+
*/
|
|
6
32
|
private resolveModulePathFallback;
|
|
33
|
+
/**
|
|
34
|
+
* Returns the file candidates we accept during the final literal filesystem probe.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* This is intentionally small and browser-entry-oriented: direct files, common
|
|
38
|
+
* JS extensions, and `index.*` entrypoints. If a package needs anything more
|
|
39
|
+
* exotic, it should resolve through the adapter or ESM export-map path above.
|
|
40
|
+
*/
|
|
41
|
+
private getFallbackCandidatePaths;
|
|
7
42
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
2
3
|
import { fileSystem } from "@ecopages/file-system";
|
|
3
4
|
import { getAppBuildAdapter } from "../../../../../build/build-adapter.js";
|
|
4
5
|
import { BaseScriptProcessor } from "../base/base-script-processor.js";
|
|
@@ -46,6 +47,15 @@ class NodeModuleScriptProcessor extends BaseScriptProcessor {
|
|
|
46
47
|
};
|
|
47
48
|
});
|
|
48
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolves a node-module script entry from the current app boundary.
|
|
52
|
+
*
|
|
53
|
+
* @remarks
|
|
54
|
+
* The build adapter remains the primary resolution surface because Bun/native
|
|
55
|
+
* host-owned builds may have a more accurate view of aliases and package
|
|
56
|
+
* ownership than core does. The local fallback only runs when that adapter
|
|
57
|
+
* resolution is unavailable.
|
|
58
|
+
*/
|
|
49
59
|
resolveModulePath(importPath, rootDir) {
|
|
50
60
|
if (path.isAbsolute(importPath) && fileSystem.exists(importPath)) {
|
|
51
61
|
return importPath;
|
|
@@ -56,13 +66,28 @@ class NodeModuleScriptProcessor extends BaseScriptProcessor {
|
|
|
56
66
|
return this.resolveModulePathFallback(importPath, rootDir);
|
|
57
67
|
}
|
|
58
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Resolves browser-owned script specifiers without relying on CommonJS resolution.
|
|
71
|
+
*
|
|
72
|
+
* @remarks
|
|
73
|
+
* This path intentionally stays ESM-first because these assets are emitted as
|
|
74
|
+
* browser scripts. We first ask Node's ESM resolver to evaluate the package
|
|
75
|
+
* export map from the app boundary. If that still fails, we fall back to a
|
|
76
|
+
* bounded filesystem probe so direct file installs and package subpaths like
|
|
77
|
+
* `pkg/client/entry` still resolve when the export map does not cover them.
|
|
78
|
+
*/
|
|
59
79
|
resolveModulePathFallback(importPath, rootDir, maxDepth = 5) {
|
|
80
|
+
try {
|
|
81
|
+
return fileURLToPath(import.meta.resolve(importPath, pathToFileURL(path.join(rootDir, "package.json")).href));
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
60
84
|
let currentDir = rootDir;
|
|
61
85
|
let remainingDepth = maxDepth;
|
|
62
86
|
while (remainingDepth >= 0) {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
87
|
+
for (const candidatePath of this.getFallbackCandidatePaths(currentDir, importPath)) {
|
|
88
|
+
if (fileSystem.exists(candidatePath)) {
|
|
89
|
+
return candidatePath;
|
|
90
|
+
}
|
|
66
91
|
}
|
|
67
92
|
const parentDir = path.dirname(currentDir);
|
|
68
93
|
if (parentDir === currentDir) {
|
|
@@ -73,6 +98,26 @@ class NodeModuleScriptProcessor extends BaseScriptProcessor {
|
|
|
73
98
|
}
|
|
74
99
|
throw new Error(`Could not resolve module '${importPath}' from '${rootDir}'`);
|
|
75
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Returns the file candidates we accept during the final literal filesystem probe.
|
|
103
|
+
*
|
|
104
|
+
* @remarks
|
|
105
|
+
* This is intentionally small and browser-entry-oriented: direct files, common
|
|
106
|
+
* JS extensions, and `index.*` entrypoints. If a package needs anything more
|
|
107
|
+
* exotic, it should resolve through the adapter or ESM export-map path above.
|
|
108
|
+
*/
|
|
109
|
+
getFallbackCandidatePaths(rootDir, importPath) {
|
|
110
|
+
const moduleBasePath = path.join(rootDir, "node_modules", importPath);
|
|
111
|
+
return [
|
|
112
|
+
moduleBasePath,
|
|
113
|
+
`${moduleBasePath}.js`,
|
|
114
|
+
`${moduleBasePath}.mjs`,
|
|
115
|
+
`${moduleBasePath}.cjs`,
|
|
116
|
+
path.join(moduleBasePath, "index.js"),
|
|
117
|
+
path.join(moduleBasePath, "index.mjs"),
|
|
118
|
+
path.join(moduleBasePath, "index.cjs")
|
|
119
|
+
];
|
|
120
|
+
}
|
|
76
121
|
}
|
|
77
122
|
export {
|
|
78
123
|
NodeModuleScriptProcessor
|