@ecopages/core 0.2.0-beta.12 → 0.2.0-beta.14
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 +1 -1
- package/package.json +26 -2
- package/src/adapters/abstract/application-adapter.d.ts +27 -3
- package/src/adapters/abstract/application-adapter.js +28 -0
- package/src/adapters/abstract/server-adapter.d.ts +3 -2
- package/src/adapters/bun/create-app.d.ts +1 -1
- package/src/adapters/bun/create-app.js +23 -6
- package/src/adapters/bun/server-adapter.d.ts +9 -2
- package/src/adapters/bun/server-adapter.js +28 -4
- package/src/adapters/create-app.d.ts +1 -12
- package/src/adapters/create-app.js +0 -58
- package/src/adapters/index.d.ts +1 -1
- package/src/adapters/index.js +1 -2
- package/src/adapters/node/create-app.d.ts +1 -1
- package/src/adapters/node/create-app.js +14 -4
- package/src/adapters/node/server-adapter.d.ts +7 -1
- package/src/adapters/node/server-adapter.js +25 -3
- package/src/adapters/shared/fs-server-response-matcher.d.ts +2 -0
- package/src/adapters/shared/fs-server-response-matcher.js +39 -17
- package/src/dev/server-ready-message.d.ts +6 -0
- package/src/dev/server-ready-message.js +8 -0
- package/src/eco/README.md +11 -0
- package/src/eco/eco-declared-component.d.ts +14 -0
- package/src/eco/eco-declared-component.js +16 -0
- package/src/eco/eco.browser.js +5 -6
- package/src/eco/eco.js +5 -6
- package/src/eco/eco.types.d.ts +2 -2
- package/src/eco/page-layout-normalization.d.ts +21 -0
- package/src/eco/page-layout-normalization.js +59 -0
- package/src/errors/index.d.ts +1 -0
- package/src/errors/index.js +7 -1
- package/src/errors/undeclared-component-dependency-error.d.ts +11 -0
- package/src/errors/undeclared-component-dependency-error.js +17 -0
- package/src/route-renderer/orchestration/component-graph.js +12 -4
- package/src/route-renderer/orchestration/document-shell-render.service.d.ts +37 -1
- package/src/route-renderer/orchestration/document-shell-render.service.js +79 -13
- package/src/route-renderer/orchestration/integration-renderer.js +5 -1
- package/src/route-renderer/orchestration/layout-shell-props.service.d.ts +20 -0
- package/src/route-renderer/orchestration/layout-shell-props.service.js +31 -0
- package/src/route-renderer/orchestration/ownership-validation.service.d.ts +1 -0
- package/src/route-renderer/orchestration/ownership-validation.service.js +18 -0
- package/src/route-renderer/orchestration/route-prepared-options.builder.d.ts +3 -1
- package/src/route-renderer/orchestration/route-prepared-options.builder.js +3 -1
- package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +3 -1
- package/src/route-renderer/orchestration/route-render-orchestrator.js +3 -3
- package/src/route-renderer/orchestration/string-markup-renderer.js +2 -1
- package/src/route-renderer/page-loading/component-dependency-collection.js +5 -2
- package/src/types/public-types.d.ts +51 -4
- package/src/utils/parse-cli-args.d.ts +1 -0
- package/src/utils/parse-cli-args.js +3 -0
- package/src/utils/server-utils.module.js +1 -0
- package/src/watchers/project-watcher-ignore.d.ts +11 -0
- package/src/watchers/project-watcher-ignore.js +16 -0
- package/src/watchers/project-watcher.js +2 -6
|
@@ -24,6 +24,7 @@ export declare class OwnershipValidationService {
|
|
|
24
24
|
* Validates foreign ownership edges reachable from the supplied route roots.
|
|
25
25
|
*/
|
|
26
26
|
validate(input: OwnershipValidationInput): OwnershipValidationError[];
|
|
27
|
+
private assertDeclaredComponentDependencies;
|
|
27
28
|
private isRegisteredIntegration;
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { assertEcoDeclaredComponent } from "../../eco/eco-declared-component.js";
|
|
1
2
|
import { mapDeclaredOwnershipGraph } from "./declared-ownership-graph.js";
|
|
3
|
+
import { walkComponentGraph } from "./component-graph.js";
|
|
2
4
|
class OwnershipValidationService {
|
|
3
5
|
appConfig;
|
|
4
6
|
/**
|
|
@@ -11,6 +13,7 @@ class OwnershipValidationService {
|
|
|
11
13
|
* Validates foreign ownership edges reachable from the supplied route roots.
|
|
12
14
|
*/
|
|
13
15
|
validate(input) {
|
|
16
|
+
this.assertDeclaredComponentDependencies(input);
|
|
14
17
|
return mapDeclaredOwnershipGraph({
|
|
15
18
|
roots: input.roots,
|
|
16
19
|
currentIntegrationName: input.currentIntegrationName,
|
|
@@ -44,6 +47,21 @@ class OwnershipValidationService {
|
|
|
44
47
|
}
|
|
45
48
|
}).flat();
|
|
46
49
|
}
|
|
50
|
+
assertDeclaredComponentDependencies(input) {
|
|
51
|
+
walkComponentGraph({
|
|
52
|
+
roots: input.roots,
|
|
53
|
+
currentIntegrationName: input.currentIntegrationName,
|
|
54
|
+
onComponent: ({ component }) => {
|
|
55
|
+
const parentFile = component.config?.__eco?.file;
|
|
56
|
+
for (const child of component.config?.dependencies?.components ?? []) {
|
|
57
|
+
if (!child) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
assertEcoDeclaredComponent(child, { parentComponentFile: parentFile });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
47
65
|
isRegisteredIntegration(integrationName, currentIntegrationName) {
|
|
48
66
|
if (integrationName === currentIntegrationName) {
|
|
49
67
|
return true;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { EcoPagesAppConfig } from '../../types/internal-types.js';
|
|
2
|
-
import type { ComponentRenderResult, EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphResult, PageMetadataProps, RouteRendererOptions } from '../../types/public-types.js';
|
|
2
|
+
import type { ComponentRenderResult, EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphResult, PageMetadataProps, EcoPageLayoutEntry, RouteRendererOptions } from '../../types/public-types.js';
|
|
3
3
|
import { type ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
|
|
4
4
|
type PreparedRenderInputs = {
|
|
5
5
|
Page: EcoPageFile['default'] | EcoPageComponent<any>;
|
|
6
6
|
HtmlTemplate: EcoComponent<HtmlTemplateProps>;
|
|
7
|
+
Layouts: EcoComponent[];
|
|
7
8
|
Layout?: EcoComponent;
|
|
9
|
+
layoutEntries?: EcoPageLayoutEntry[];
|
|
8
10
|
props: Record<string, unknown>;
|
|
9
11
|
metadata: PageMetadataProps;
|
|
10
12
|
integrationSpecificProps: Record<string, unknown>;
|
|
@@ -11,7 +11,7 @@ function buildPreparedRenderOptions(input) {
|
|
|
11
11
|
componentRender,
|
|
12
12
|
appConfig
|
|
13
13
|
} = input;
|
|
14
|
-
const { Page, HtmlTemplate, Layout, props, metadata, integrationSpecificProps } = resolvedInputs;
|
|
14
|
+
const { Page, HtmlTemplate, Layouts, Layout, layoutEntries, props, metadata, integrationSpecificProps } = resolvedInputs;
|
|
15
15
|
const dedupedDependencies = dedupeProcessedAssets(allDependencies);
|
|
16
16
|
const pagePackage = createPagePackage(dedupedDependencies, { pageBrowserGraph });
|
|
17
17
|
const pageProps = {
|
|
@@ -31,7 +31,9 @@ function buildPreparedRenderOptions(input) {
|
|
|
31
31
|
pagePackage,
|
|
32
32
|
componentRender,
|
|
33
33
|
HtmlTemplate,
|
|
34
|
+
Layouts,
|
|
34
35
|
Layout,
|
|
36
|
+
layoutEntries,
|
|
35
37
|
props,
|
|
36
38
|
Page,
|
|
37
39
|
metadata,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EcoPagesAppConfig } from '../../types/internal-types.js';
|
|
2
|
-
import type { ComponentRenderResult, EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult } from '../../types/public-types.js';
|
|
2
|
+
import type { ComponentRenderResult, EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult, EcoPageLayoutEntry } from '../../types/public-types.js';
|
|
3
3
|
import type { AssetProcessingService, ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
|
|
4
4
|
import type { HtmlDocumentContribution } from '../../services/html/html-transformer.service.js';
|
|
5
5
|
import { OwnershipValidationService } from './ownership-validation.service.js';
|
|
@@ -7,7 +7,9 @@ import { PageBrowserGraphService } from './page-browser-graph.service.js';
|
|
|
7
7
|
export type RouteRenderOrchestratorResolvedInputs = {
|
|
8
8
|
Page: EcoPageFile['default'] | EcoPageComponent<any>;
|
|
9
9
|
HtmlTemplate: EcoComponent<HtmlTemplateProps>;
|
|
10
|
+
Layouts: EcoComponent[];
|
|
10
11
|
Layout?: EcoComponent;
|
|
12
|
+
layoutEntries?: EcoPageLayoutEntry[];
|
|
11
13
|
props: Record<string, unknown>;
|
|
12
14
|
metadata: PageMetadataProps;
|
|
13
15
|
integrationSpecificProps: Record<string, unknown>;
|
|
@@ -28,17 +28,17 @@ class RouteRenderOrchestrator {
|
|
|
28
28
|
*/
|
|
29
29
|
async prepareRenderOptions(routeOptions, adapter) {
|
|
30
30
|
const resolvedInputs = await adapter.resolveRouteRenderInputs(routeOptions);
|
|
31
|
-
const { Page, HtmlTemplate, Layout } = resolvedInputs;
|
|
31
|
+
const { Page, HtmlTemplate, Layouts, Layout } = resolvedInputs;
|
|
32
32
|
const validationErrors = this.ownershipValidationService.validate({
|
|
33
33
|
currentIntegrationName: adapter.name,
|
|
34
34
|
roots: [
|
|
35
35
|
{ component: HtmlTemplate, source: "html-template" },
|
|
36
|
-
...
|
|
36
|
+
...Layouts.map((layout) => ({ component: layout, source: "layout" })),
|
|
37
37
|
{ component: Page, source: "page" }
|
|
38
38
|
]
|
|
39
39
|
});
|
|
40
40
|
throwIfOwnershipInvalid(validationErrors);
|
|
41
|
-
const componentsToResolve =
|
|
41
|
+
const componentsToResolve = [HtmlTemplate, ...Layouts, Page];
|
|
42
42
|
const [{ resolvedDependencies }, pageBrowserGraph, componentRender] = await Promise.all([
|
|
43
43
|
adapter.resolveRouteDependencies({
|
|
44
44
|
components: componentsToResolve
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IntegrationRenderer } from "./integration-renderer.js";
|
|
2
|
+
import { resolveInnermostPageLayout } from "./layout-shell-props.service.js";
|
|
2
3
|
class StringMarkupRenderer extends IntegrationRenderer {
|
|
3
4
|
async renderComponent(input) {
|
|
4
5
|
if (typeof input.component !== "function") {
|
|
@@ -42,7 +43,7 @@ class StringMarkupRenderer extends IntegrationRenderer {
|
|
|
42
43
|
view,
|
|
43
44
|
props,
|
|
44
45
|
ctx,
|
|
45
|
-
layout: view.config?.
|
|
46
|
+
layout: resolveInnermostPageLayout(view.config?.layouts)
|
|
46
47
|
});
|
|
47
48
|
} catch (error) {
|
|
48
49
|
throw this.createRenderError("Error rendering view", error);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { assertEcoDeclaredComponent } from "../../eco/eco-declared-component.js";
|
|
2
3
|
import { AssetFactory } from "../../services/assets/asset-processing-service/index.js";
|
|
3
4
|
import { extractEcopagesVirtualImports } from "./ecopages-virtual-imports.js";
|
|
4
5
|
import { collectDeclaredAssetEntries } from "./declared-asset-collection.js";
|
|
@@ -103,9 +104,11 @@ function collectComponentDependencies(options) {
|
|
|
103
104
|
});
|
|
104
105
|
if (dependenciesConfig?.components) {
|
|
105
106
|
for (const nestedComponent of dependenciesConfig.components) {
|
|
106
|
-
if (nestedComponent
|
|
107
|
-
|
|
107
|
+
if (!nestedComponent) {
|
|
108
|
+
continue;
|
|
108
109
|
}
|
|
110
|
+
assertEcoDeclaredComponent(nestedComponent, { parentComponentFile: file });
|
|
111
|
+
collect(nestedComponent.config);
|
|
109
112
|
}
|
|
110
113
|
}
|
|
111
114
|
};
|
|
@@ -334,8 +334,21 @@ export type EcoComponentDependencies = {
|
|
|
334
334
|
* to express explicit module imports for client bundles.
|
|
335
335
|
*/
|
|
336
336
|
modules?: string[];
|
|
337
|
-
|
|
337
|
+
/**
|
|
338
|
+
* Child components whose assets and foreign-child graph are collected transitively.
|
|
339
|
+
* Each entry must be an `eco.component()`, `eco.layout()`, or `eco.html()` result
|
|
340
|
+
* with plugin-injected `config.__eco` metadata.
|
|
341
|
+
*/
|
|
342
|
+
components?: EcoDeclaredComponent[];
|
|
338
343
|
};
|
|
344
|
+
/**
|
|
345
|
+
* Component returned from `eco.component()`, `eco.layout()`, or `eco.html()`.
|
|
346
|
+
*
|
|
347
|
+
* @remarks
|
|
348
|
+
* Used for `dependencies.components` and eco factory return types. Plugin-injected
|
|
349
|
+
* `config.__eco` is enforced at runtime via `isEcoDeclaredComponent()`, not by this alias.
|
|
350
|
+
*/
|
|
351
|
+
export type EcoDeclaredComponent<P = any, R = any> = EcoComponent<P, R>;
|
|
339
352
|
export type EcoPagesElement = string | Promise<string>;
|
|
340
353
|
/**
|
|
341
354
|
* Serializable child payloads accepted by cross-integration deferred rendering.
|
|
@@ -401,11 +414,13 @@ export type EcoComponentConfig = {
|
|
|
401
414
|
* );
|
|
402
415
|
*
|
|
403
416
|
* // Page using the layout
|
|
404
|
-
* const MyPage = () => <h1>Hello</h1
|
|
405
|
-
* MyPage.config = { layout: Layout };
|
|
417
|
+
* const MyPage = eco.page({ layout: Layout, render: () => <h1>Hello</h1> });
|
|
406
418
|
* ```
|
|
407
419
|
*/
|
|
408
|
-
layout
|
|
420
|
+
/** Normalized outer→inner layout stack from `eco.page({ layout: [...] })`. */
|
|
421
|
+
layouts?: EcoDeclaredComponent[];
|
|
422
|
+
/** Layout entries retained for per-tier prop factories. */
|
|
423
|
+
layoutEntries?: EcoPageLayoutEntry[];
|
|
409
424
|
dependencies?: EcoComponentDependencies;
|
|
410
425
|
/**
|
|
411
426
|
* Internal: Resolved lazy scripts grouped by trigger.
|
|
@@ -529,6 +544,36 @@ export interface HtmlTemplateProps<T = EcoPagesElement> extends PageHeadProps<T>
|
|
|
529
544
|
headContent?: T;
|
|
530
545
|
pageProps: Record<string, unknown>;
|
|
531
546
|
}
|
|
547
|
+
/**
|
|
548
|
+
* Request-scoped context available to layout prop factories on `eco.page`.
|
|
549
|
+
*/
|
|
550
|
+
export type LayoutPropsContext = {
|
|
551
|
+
params?: Record<string, string>;
|
|
552
|
+
query?: Record<string, string>;
|
|
553
|
+
locals?: RequestLocals;
|
|
554
|
+
};
|
|
555
|
+
/**
|
|
556
|
+
* One layout tier in a page layout stack (outer→inner).
|
|
557
|
+
*/
|
|
558
|
+
export type EcoPageLayoutEntry<E = EcoPagesElement> = {
|
|
559
|
+
component: EcoDeclaredComponent<any, E>;
|
|
560
|
+
props?: (context: LayoutPropsContext) => Record<string, unknown>;
|
|
561
|
+
};
|
|
562
|
+
/**
|
|
563
|
+
* Layout declaration accepted by `eco.page()`.
|
|
564
|
+
*
|
|
565
|
+
* @remarks
|
|
566
|
+
* Array order is outer→inner, matching Next.js App Router segment nesting.
|
|
567
|
+
*/
|
|
568
|
+
export type EcoPageLayoutSpec<E = EcoPagesElement> = EcoDeclaredComponent<any, E> | EcoPageLayoutEntry<E>;
|
|
569
|
+
/**
|
|
570
|
+
* One or more layout tiers for `eco.page({ layout })`.
|
|
571
|
+
*
|
|
572
|
+
* @remarks
|
|
573
|
+
* When an array, order is **outer → inner** (outermost layout wraps all inner tiers).
|
|
574
|
+
* Normalized at factory time to `config.layouts` and `config.layoutEntries`.
|
|
575
|
+
*/
|
|
576
|
+
export type EcoPageLayouts<E = EcoPagesElement> = EcoPageLayoutSpec<E> | EcoPageLayoutSpec<E>[];
|
|
532
577
|
/**
|
|
533
578
|
* Layout components accepted by pages.
|
|
534
579
|
*
|
|
@@ -746,7 +791,9 @@ export type IntegrationRendererRenderOptions<C = EcoPagesElement> = RouteRendere
|
|
|
746
791
|
metadata: PageMetadataProps;
|
|
747
792
|
HtmlTemplate: EcoHtmlComponent<C>;
|
|
748
793
|
Page: EcoComponent<PageProps, C>;
|
|
794
|
+
Layouts?: EcoDeclaredComponent[];
|
|
749
795
|
Layout?: EcoPageLayoutComponent<any>;
|
|
796
|
+
layoutEntries?: EcoPageLayoutEntry[];
|
|
750
797
|
dependencies?: EcoComponentDependencies;
|
|
751
798
|
resolvedDependencies: ProcessedAsset[];
|
|
752
799
|
pagePackage?: PagePackageResult;
|
|
@@ -8,6 +8,7 @@ function getEmbeddedRuntimeCommandOptions() {
|
|
|
8
8
|
start: !isDevelopment,
|
|
9
9
|
dev: isDevelopment,
|
|
10
10
|
force: false,
|
|
11
|
+
serveOnly: false,
|
|
11
12
|
port: void 0,
|
|
12
13
|
hostname: void 0,
|
|
13
14
|
reactFastRefresh: void 0
|
|
@@ -27,6 +28,7 @@ function parseCliArgs(options = {}) {
|
|
|
27
28
|
preview: { type: "boolean" },
|
|
28
29
|
build: { type: "boolean" },
|
|
29
30
|
force: { type: "boolean" },
|
|
31
|
+
"serve-only": { type: "boolean" },
|
|
30
32
|
port: { type: "string" },
|
|
31
33
|
hostname: { type: "string" },
|
|
32
34
|
"react-fast-refresh": { type: "boolean" }
|
|
@@ -51,6 +53,7 @@ function parseCliArgs(options = {}) {
|
|
|
51
53
|
start: isStartCommand,
|
|
52
54
|
dev: isDevCommand,
|
|
53
55
|
force: !!values.force,
|
|
56
|
+
serveOnly: !!values["serve-only"] || process.env.ECOPAGES_PREVIEW_SERVE_ONLY === "true",
|
|
54
57
|
port: values.port ? Number(values.port) : void 0,
|
|
55
58
|
hostname: values.hostname,
|
|
56
59
|
reactFastRefresh: values["react-fast-refresh"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type ProjectWatcherIgnorePaths = {
|
|
2
|
+
workDir: string;
|
|
3
|
+
distDir: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* @remarks
|
|
7
|
+
* chokidar v4+ no longer treats glob strings in `ignored` as patterns. A path
|
|
8
|
+
* predicate keeps node_modules, VCS metadata, and scoped artifact dirs out of
|
|
9
|
+
* the watch tree without descending into symlinked monorepo node_modules.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createProjectWatcherIgnorePredicate(absolutePaths: ProjectWatcherIgnorePaths): (watchedPath: string) => boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
function createProjectWatcherIgnorePredicate(absolutePaths) {
|
|
3
|
+
const ignoredPrefixes = [absolutePaths.workDir, absolutePaths.distDir];
|
|
4
|
+
return (watchedPath) => {
|
|
5
|
+
const segments = watchedPath.split(path.sep);
|
|
6
|
+
if (segments.includes("node_modules") || segments.includes(".git")) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
return ignoredPrefixes.some(
|
|
10
|
+
(prefix) => watchedPath === prefix || watchedPath.startsWith(`${prefix}${path.sep}`)
|
|
11
|
+
);
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
createProjectWatcherIgnorePredicate
|
|
16
|
+
};
|
|
@@ -3,6 +3,7 @@ import chokidar, {} from "chokidar";
|
|
|
3
3
|
import { fileSystem } from "@ecopages/file-system";
|
|
4
4
|
import { appLogger } from "../global/app-logger.js";
|
|
5
5
|
import { DevelopmentInvalidationService } from "../services/invalidation/development-invalidation.service.js";
|
|
6
|
+
import { createProjectWatcherIgnorePredicate } from "./project-watcher-ignore.js";
|
|
6
7
|
class ProjectWatcher {
|
|
7
8
|
/**
|
|
8
9
|
* Duplicate identical watcher events within this window are ignored.
|
|
@@ -261,12 +262,7 @@ class ProjectWatcher {
|
|
|
261
262
|
for (const watchPath of this.appConfig.additionalWatchPaths) {
|
|
262
263
|
processorPaths.add(watchPath);
|
|
263
264
|
}
|
|
264
|
-
const ignored =
|
|
265
|
-
"**/node_modules/**",
|
|
266
|
-
"**/.git/**",
|
|
267
|
-
path.join(this.appConfig.absolutePaths.workDir, "**"),
|
|
268
|
-
path.join(this.appConfig.absolutePaths.distDir, "**")
|
|
269
|
-
];
|
|
265
|
+
const ignored = createProjectWatcherIgnorePredicate(this.appConfig.absolutePaths);
|
|
270
266
|
this.watcher = chokidar.watch(Array.from(processorPaths), {
|
|
271
267
|
ignoreInitial: true,
|
|
272
268
|
ignorePermissionErrors: true,
|