@ecopages/core 0.2.0-rc.3 → 0.2.0-rc.5
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 +5 -2
- package/package.json +7 -4
- package/src/build/contracts/content-virtual-modules.d.ts +3 -1
- package/src/build/contracts/content-virtual-modules.js +8 -0
- package/src/css-imports.d.ts +4 -0
- package/src/declarations.d.ts +2 -5
- package/src/dev/README.md +2 -0
- package/src/dev/transform-server/dev-transform-server.d.ts +6 -0
- package/src/dev/transform-server/dev-transform-server.js +16 -5
- package/src/eco/README.md +71 -5
- package/src/eco/component-identity.d.ts +3 -2
- package/src/eco/component-identity.js +5 -2
- package/src/eco/discovered-dependencies.d.ts +45 -0
- package/src/eco/discovered-dependencies.js +87 -0
- package/src/eco/eco.js +2 -0
- package/src/eco/eco.types.d.ts +18 -2
- package/src/eco/page-dependency-contributions.d.ts +19 -0
- package/src/eco/page-dependency-contributions.js +52 -0
- package/src/index.browser.d.ts +3 -0
- package/src/index.browser.js +3 -0
- package/src/index.d.ts +4 -1
- package/src/index.js +4 -1
- package/src/plugins/README.md +7 -1
- package/src/plugins/component-import-discovery.d.ts +19 -0
- package/src/plugins/component-import-discovery.js +203 -0
- package/src/plugins/eco-component-meta-plugin.d.ts +3 -1
- package/src/plugins/eco-component-meta-plugin.js +101 -16
- package/src/plugins/tsconfig-import-resolver.d.ts +3 -1
- package/src/plugins/tsconfig-import-resolver.js +4 -2
- package/src/route-renderer/GRAPH.md +1 -1
- package/src/route-renderer/README.md +8 -0
- package/src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.d.ts +8 -0
- package/src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.js +9 -1
- package/src/route-renderer/orchestration/integration-renderer.d.ts +2 -2
- package/src/route-renderer/orchestration/integration-renderer.js +8 -9
- package/src/route-renderer/orchestration/ownership-graph/component-graph-collectors.d.ts +1 -4
- package/src/route-renderer/orchestration/ownership-graph/component-graph-collectors.js +0 -66
- package/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.js +5 -8
- package/src/route-renderer/page-loading/component-dependency-collection.d.ts +6 -2
- package/src/route-renderer/page-loading/component-dependency-collection.js +29 -5
- package/src/route-renderer/page-loading/file-scoped-dependency-components.d.ts +11 -1
- package/src/route-renderer/page-loading/file-scoped-dependency-components.js +40 -6
- package/src/route-renderer/page-loading/resolved-page-dependencies.d.ts +1 -2
- package/src/route-renderer/page-loading/resolved-page-dependencies.js +8 -7
- package/src/services/module-loading/page-module-import.service.d.ts +2 -0
- package/src/services/module-loading/page-module-import.service.js +2 -1
- package/src/static-site-generator/README.md +3 -1
- package/src/types/public-types.d.ts +1 -1
package/README.md
CHANGED
|
@@ -13,13 +13,16 @@ Ecopages is an extensible static site generator (SSG) built around a Bun-first c
|
|
|
13
13
|
|
|
14
14
|
## Current Architecture
|
|
15
15
|
|
|
16
|
+
Projects load shared ambient types through `@ecopages/core/declarations`. This entry references `src/css-imports.d.ts`, which keeps the wildcard CSS declaration in a standalone ambient file so side-effect CSS imports typecheck without per-project declarations.
|
|
17
|
+
|
|
16
18
|
The current core package is organized around app-owned runtime state and explicit service boundaries.
|
|
17
19
|
|
|
18
20
|
The important ownership rules are:
|
|
19
21
|
|
|
20
22
|
- `ConfigBuilder.build()` finalizes app-owned build and runtime services.
|
|
21
23
|
- Component identity attribution is one shared source transform for server, browser, and HMR compilation paths: after a lexical `eco.` gate, it uses Oxc to wrap supported factory options with `bindComponentIdentity()`, which factories retain as `config.identity`.
|
|
22
|
-
-
|
|
24
|
+
- Direct local Eco Component imports (including named `export { X } from` barrels) and relative side-effect CSS imports supply Dependencies by default through the shared transform and existing collector; explicit asset declarations override inference, and inferred styles stay keyed by identity until collection. See [eco](src/eco/README.md) and [Plugin Contracts](src/plugins/README.md).
|
|
25
|
+
- Browser bundling and server module loading are separate paths. SSR-enabled lazy scripts execute on the server before rendering and in the browser only on their configured trigger.
|
|
23
26
|
- runtime hosts stay thin and delegate framework work into core services.
|
|
24
27
|
- HMR and invalidation use shared graph-aware services instead of runtime-specific ad hoc wiring.
|
|
25
28
|
|
|
@@ -83,7 +86,7 @@ Use this package README as the top-level map, then drill into the focused subsys
|
|
|
83
86
|
- `src/build/README.md`: build adapter, executor, development build coordination, and standalone Node server packaging
|
|
84
87
|
- `src/services/README.md`: cross-cutting runtime services and orchestration helpers
|
|
85
88
|
- `src/adapters/README.md`: Bun, Node, and shared adapter boundaries
|
|
86
|
-
- `src/dev/README.md`: dev transform server
|
|
89
|
+
- `src/dev/README.md`: dev transform server, on-demand client delivery, and invalidation during compilation
|
|
87
90
|
- `src/hmr/README.md`: HMR strategy and update-layer ownership
|
|
88
91
|
- `src/router/README.md`: route discovery, matching, and browser navigation coordination
|
|
89
92
|
- `src/route-renderer/README.md`: rendering orchestration and dependency resolution
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/core",
|
|
3
|
-
"version": "0.2.0-rc.
|
|
3
|
+
"version": "0.2.0-rc.5",
|
|
4
4
|
"description": "Core package for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"main": "./src/index.js",
|
|
12
12
|
"type": "module",
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
13
16
|
"sideEffects": false,
|
|
14
17
|
"repository": {
|
|
15
18
|
"type": "git",
|
|
@@ -17,7 +20,7 @@
|
|
|
17
20
|
"directory": "packages/core"
|
|
18
21
|
},
|
|
19
22
|
"dependencies": {
|
|
20
|
-
"@ecopages/file-system": "0.2.0-rc.
|
|
23
|
+
"@ecopages/file-system": "0.2.0-rc.5",
|
|
21
24
|
"@ecopages/logger": "^0.2.3",
|
|
22
25
|
"@ecopages/scripts-injector": "^0.1.5",
|
|
23
26
|
"@oxc-project/runtime": "0.141.0",
|
|
@@ -31,7 +34,7 @@
|
|
|
31
34
|
"@standard-schema/utils": "^0.3.0"
|
|
32
35
|
},
|
|
33
36
|
"peerDependencies": {
|
|
34
|
-
"@ecopages/dev-toolbar": "0.2.0-rc.
|
|
37
|
+
"@ecopages/dev-toolbar": "0.2.0-rc.5"
|
|
35
38
|
},
|
|
36
39
|
"peerDependenciesMeta": {
|
|
37
40
|
"@ecopages/dev-toolbar": {
|
|
@@ -631,4 +634,4 @@
|
|
|
631
634
|
}
|
|
632
635
|
},
|
|
633
636
|
"types": "./src/index.d.ts"
|
|
634
|
-
}
|
|
637
|
+
}
|
|
@@ -4,9 +4,11 @@ export declare const CONTENT_VIRTUAL_MODULE_PREFIX = "ecopages:content";
|
|
|
4
4
|
export declare const CONTENT_VIRTUAL_MODULE_PATTERN: RegExp;
|
|
5
5
|
/** Matches `ecopages:content/<collection>/server` component resolver specifiers. */
|
|
6
6
|
export declare const CONTENT_SERVER_VIRTUAL_MODULE_PATTERN: RegExp;
|
|
7
|
+
/** Matches browser-only `ecopages:content/<collection>/browser` component loader specifiers. */
|
|
8
|
+
export declare const CONTENT_BROWSER_VIRTUAL_MODULE_PATTERN: RegExp;
|
|
7
9
|
export type ParsedCollectionSpecifier = {
|
|
8
10
|
collectionName: string;
|
|
9
|
-
variant: 'entries' | 'server';
|
|
11
|
+
variant: 'entries' | 'server' | 'browser';
|
|
10
12
|
};
|
|
11
13
|
export declare function parseCollectionSpecifier(specifier: string): ParsedCollectionSpecifier | null;
|
|
12
14
|
export declare function isContentServerVirtualModule(specifier: string): boolean;
|
|
@@ -4,6 +4,8 @@ export const CONTENT_VIRTUAL_MODULE_PREFIX = 'ecopages:content';
|
|
|
4
4
|
export const CONTENT_VIRTUAL_MODULE_PATTERN = /^ecopages:content\/[a-z][a-z0-9-]*$/;
|
|
5
5
|
/** Matches `ecopages:content/<collection>/server` component resolver specifiers. */
|
|
6
6
|
export const CONTENT_SERVER_VIRTUAL_MODULE_PATTERN = /^ecopages:content\/[a-z][a-z0-9-]+\/server$/;
|
|
7
|
+
/** Matches browser-only `ecopages:content/<collection>/browser` component loader specifiers. */
|
|
8
|
+
export const CONTENT_BROWSER_VIRTUAL_MODULE_PATTERN = /^ecopages:content\/[a-z][a-z0-9-]+\/browser$/;
|
|
7
9
|
export function parseCollectionSpecifier(specifier) {
|
|
8
10
|
const normalized = specifier.startsWith(`${CONTENT_VIRTUAL_MODULE_PREFIX}/`)
|
|
9
11
|
? specifier.slice(`${CONTENT_VIRTUAL_MODULE_PREFIX}/`.length)
|
|
@@ -19,6 +21,12 @@ export function parseCollectionSpecifier(specifier) {
|
|
|
19
21
|
variant: 'server',
|
|
20
22
|
};
|
|
21
23
|
}
|
|
24
|
+
if (normalized.endsWith('/browser')) {
|
|
25
|
+
return {
|
|
26
|
+
collectionName: normalized.slice(0, -'/browser'.length),
|
|
27
|
+
variant: 'browser',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
22
30
|
return {
|
|
23
31
|
collectionName: normalized,
|
|
24
32
|
variant: 'entries',
|
package/src/declarations.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/// <reference path="./css-imports.d.js" />
|
|
2
|
+
|
|
1
3
|
import type { EcoPagesAppConfig } from './types/internal-types';
|
|
2
4
|
import type { EcoNavigationRuntime } from './router/client/navigation-coordinator';
|
|
3
5
|
|
|
@@ -25,8 +27,3 @@ declare global {
|
|
|
25
27
|
__ECO_PAGES__?: EcoPagesWindowRuntime;
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
|
-
|
|
29
|
-
declare module '*.css' {
|
|
30
|
-
const styles: string;
|
|
31
|
-
export default styles;
|
|
32
|
-
}
|
package/src/dev/README.md
CHANGED
|
@@ -22,3 +22,5 @@ Install the client package in the app project. Core resolves it from the applica
|
|
|
22
22
|
Disable per project with `devToolbar: { enabled: false }`, or per process with `ECOPAGES_DEV_TOOLBAR=false`.
|
|
23
23
|
|
|
24
24
|
Server wiring: [dev-toolbar/README.md](../dev-toolbar/README.md) (`DevToolbarHost`, manifest, runtime bundling, HTML injection). Bring-your-own toolbars export `src/bootstrap.ts` and use the same `devToolbar.package` config key.
|
|
25
|
+
|
|
26
|
+
Dev transform requests retry when their source changes or is invalidated during compilation. Concurrent requests share one in-flight compile per source, including across source and global invalidation; stale attempts release their slot before retrying. Only results for the current source snapshot enter the module cache, so rapid edits cannot label stale browser code with a newer source hash.
|
|
@@ -24,6 +24,7 @@ export declare class DevTransformServer {
|
|
|
24
24
|
private readonly cache;
|
|
25
25
|
private readonly inFlight;
|
|
26
26
|
private cacheGeneration;
|
|
27
|
+
private readonly sourceGenerations;
|
|
27
28
|
constructor(options: DevTransformServerOptions);
|
|
28
29
|
registerModule(sourcePath: string): string;
|
|
29
30
|
getRegisteredSourcePath(moduleUrl: string): string | undefined;
|
|
@@ -31,6 +32,11 @@ export declare class DevTransformServer {
|
|
|
31
32
|
private rebuildRuntimeSpecifierMap;
|
|
32
33
|
private resolveVendorBundlePlugins;
|
|
33
34
|
getWatchedModules(): ReadonlyMap<string, string>;
|
|
35
|
+
/**
|
|
36
|
+
* @remarks
|
|
37
|
+
* Marks pending work stale while preserving one in-flight compile per source.
|
|
38
|
+
* Requests awaiting that work retry against the current source when it completes.
|
|
39
|
+
*/
|
|
34
40
|
invalidateSource(sourcePath: string): void;
|
|
35
41
|
invalidateAll(): void;
|
|
36
42
|
reset(): void;
|
|
@@ -26,6 +26,7 @@ export class DevTransformServer {
|
|
|
26
26
|
cache = new Map();
|
|
27
27
|
inFlight = new Map();
|
|
28
28
|
cacheGeneration = 0;
|
|
29
|
+
sourceGenerations = new Map();
|
|
29
30
|
constructor(options) {
|
|
30
31
|
this.appConfig = options.appConfig;
|
|
31
32
|
this.onModuleDependencies = options.onModuleDependencies;
|
|
@@ -81,14 +82,20 @@ export class DevTransformServer {
|
|
|
81
82
|
getWatchedModules() {
|
|
82
83
|
return this.urlToSource;
|
|
83
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* @remarks
|
|
87
|
+
* Marks pending work stale while preserving one in-flight compile per source.
|
|
88
|
+
* Requests awaiting that work retry against the current source when it completes.
|
|
89
|
+
*/
|
|
84
90
|
invalidateSource(sourcePath) {
|
|
85
91
|
const normalized = path.resolve(sourcePath);
|
|
86
92
|
this.cache.delete(normalized);
|
|
93
|
+
this.sourceGenerations.set(normalized, (this.sourceGenerations.get(normalized) ?? 0) + 1);
|
|
87
94
|
}
|
|
88
95
|
invalidateAll() {
|
|
89
96
|
this.cacheGeneration += 1;
|
|
90
97
|
this.cache.clear();
|
|
91
|
-
this.
|
|
98
|
+
this.sourceGenerations.clear();
|
|
92
99
|
this.vendorRegistry.invalidateAll();
|
|
93
100
|
}
|
|
94
101
|
reset() {
|
|
@@ -169,22 +176,26 @@ export class DevTransformServer {
|
|
|
169
176
|
return entry;
|
|
170
177
|
}
|
|
171
178
|
const generation = this.cacheGeneration;
|
|
179
|
+
const sourceGeneration = this.sourceGenerations.get(normalized) ?? 0;
|
|
172
180
|
const promise = this.bundler
|
|
173
181
|
.transpileModule(normalized)
|
|
174
182
|
.then((result) => {
|
|
175
|
-
if (generation !== this.cacheGeneration
|
|
176
|
-
|
|
183
|
+
if (generation !== this.cacheGeneration ||
|
|
184
|
+
sourceGeneration !== (this.sourceGenerations.get(normalized) ?? 0) ||
|
|
185
|
+
fileSystem.hash(normalized) !== sourceHash) {
|
|
186
|
+
return undefined;
|
|
177
187
|
}
|
|
178
188
|
if (result.dependencies) {
|
|
179
189
|
this.onModuleDependencies?.(normalized, result.dependencies);
|
|
180
190
|
}
|
|
181
|
-
const entry = { code: result.code, sourceHash
|
|
191
|
+
const entry = { code: result.code, sourceHash };
|
|
182
192
|
this.cache.set(normalized, entry);
|
|
183
193
|
return entry;
|
|
184
194
|
})
|
|
185
195
|
.finally(() => {
|
|
186
196
|
this.inFlight.delete(normalized);
|
|
187
|
-
})
|
|
197
|
+
})
|
|
198
|
+
.then((entry) => entry ?? this.materialize(normalized));
|
|
188
199
|
this.inFlight.set(normalized, promise);
|
|
189
200
|
return promise;
|
|
190
201
|
}
|
package/src/eco/README.md
CHANGED
|
@@ -20,6 +20,57 @@ Layouts are assigned explicitly on each `eco.page({ layout })` call — either o
|
|
|
20
20
|
|
|
21
21
|
EcoPages does **not** infer layouts from `src/layouts/` file paths or route segment directories. A file under `src/layouts/` is only used when a page imports it and passes it to `layout`.
|
|
22
22
|
|
|
23
|
+
## Dependency discovery
|
|
24
|
+
|
|
25
|
+
Discovery is enabled by default for modules declaring `eco.component()`, `eco.layout()`, `eco.html()`, or `eco.page()`, as well as MDX documents compiled through `@ecopages/mdx/core`.
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { eco } from '@ecopages/core';
|
|
29
|
+
import { Counter } from './counter';
|
|
30
|
+
import './page.css';
|
|
31
|
+
|
|
32
|
+
export default eco.page({
|
|
33
|
+
render: () => <Counter />,
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Direct static named/default imports of local Eco Components contribute their Dependencies transitively. Configured TypeScript path aliases and relative imports are supported for both Components and CSS stylesheets (e.g., `import '@/components/button'` or `import '@/styles/main.css'`). Side-effect CSS imports become stylesheet Dependencies; the shared transform removes the imports so the existing asset pipeline owns delivery in server and browser builds. In MDX documents, top-level component and CSS imports are discovered while markdown code blocks and dynamic imports within functions are safely ignored.
|
|
38
|
+
|
|
39
|
+
Discovery is conservative and module-scoped: every Eco declaration in a file shares its imported Components and styles, even when a render condition omits a Component. It does not infer which Components actually render. **Best practice:** author **one `eco.component()` per file**. Co-locating multiple components in a single file will cause all declared components in that file to share discovered dependencies.
|
|
40
|
+
|
|
41
|
+
Imported Layouts contribute assets; only the explicit `layout` option controls Layout composition.
|
|
42
|
+
|
|
43
|
+
Explicit `dependencies` remain supported. Explicit Components come first, followed by discovered Components in import order, with duplicate Components removed. Explicit stylesheet declarations take precedence over discovered references to the same resolved file, preserving their attributes and order. Discovered stylesheets are **not** written into `config.dependencies.stylesheets`; they stay on Component identity until collection. Inferred styles follow explicit styles and are emitted once per collection. Circular dependency traversal is guarded by Component config identity, so two Components in one file remain distinct.
|
|
44
|
+
|
|
45
|
+
When a Page combines its own relative assets with a content entry, use `mergePageDependencies()`. Do not object-spread the two bags: spread copies `ownerFile` onto the other side's relative paths.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { eco, mergePageDependencies } from '@ecopages/core';
|
|
49
|
+
import { getEntryDependencies } from 'ecopages:content/posts/server';
|
|
50
|
+
|
|
51
|
+
export default eco.page({
|
|
52
|
+
dependencies: async ({ props }) =>
|
|
53
|
+
mergePageDependencies({ stylesheets: ['./post.css'] }, await getEntryDependencies(props.slug)),
|
|
54
|
+
render: () => <article />,
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Browser scripts remain explicit:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
scripts: [
|
|
62
|
+
{
|
|
63
|
+
src: './counter.script.ts',
|
|
64
|
+
ssr: true,
|
|
65
|
+
lazy: { 'on:visible': true },
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For Lit, this registers the custom element before server rendering while delaying browser execution until visibility. `ssr: true` does not force eager browser loading. To load eagerly, omit `lazy`. This corrects the previous behavior that emitted an extra eager script for SSR-enabled lazy entries.
|
|
71
|
+
|
|
72
|
+
Named barrel re-exports (`export { Counter } from './counter'`) are followed for the imported binding only. Successful named re-export hops are watch paths, so retargeting a barrel updates page assets without editing the importing Page. Discovery does not follow `export * from './components'`, dynamic imports, namespace imports (`import * as`), package Components/CSS, CSS Modules, or custom import attributes. `export *` would pull an entire kit into the page asset graph; keep that explicit with `dependencies.components`. Plain-function modules retain their existing behavior. Ordinary utility imports do not become browser script entries. Missing supported imports report the owner file and import specifier.
|
|
73
|
+
|
|
23
74
|
## Component Patterns
|
|
24
75
|
|
|
25
76
|
EcoPages supports two approaches for creating components, each suited for different use cases:
|
|
@@ -546,9 +597,6 @@ import { Counter } from '@/components/counter';
|
|
|
546
597
|
|
|
547
598
|
export default eco.page({
|
|
548
599
|
layout: BaseLayout,
|
|
549
|
-
dependencies: {
|
|
550
|
-
components: [Counter],
|
|
551
|
-
},
|
|
552
600
|
metadata: () => ({
|
|
553
601
|
title: 'Home',
|
|
554
602
|
description: 'Welcome to EcoPages',
|
|
@@ -562,6 +610,8 @@ export default eco.page({
|
|
|
562
610
|
});
|
|
563
611
|
```
|
|
564
612
|
|
|
613
|
+
The static `Counter` import is discovered automatically. You do not list it again in `dependencies.components`. Lazy scripts stay on the Component.
|
|
614
|
+
|
|
565
615
|
## Type Definitions
|
|
566
616
|
|
|
567
617
|
```typescript
|
|
@@ -578,9 +628,25 @@ type DependencyEntry = {
|
|
|
578
628
|
interface EcoComponentDependencies {
|
|
579
629
|
scripts?: Array<string | DependencyEntry>;
|
|
580
630
|
stylesheets?: Array<string | DependencyEntry>;
|
|
581
|
-
|
|
631
|
+
modules?: string[];
|
|
632
|
+
/** Declared eco components only — see `EcoDeclaredComponent`. */
|
|
633
|
+
components?: EcoDeclaredComponent[];
|
|
582
634
|
}
|
|
583
635
|
|
|
636
|
+
type FileOwnedDependencyContribution = EcoComponentDependencies & {
|
|
637
|
+
ownerFile?: string;
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
type PageDependenciesResult = FileOwnedDependencyContribution & {
|
|
641
|
+
contributions?: FileOwnedDependencyContribution[];
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
type GetPageDependencies<T> = (context: {
|
|
645
|
+
props: PagePropsFor<T>;
|
|
646
|
+
params?: Record<string, string>;
|
|
647
|
+
query?: Record<string, string>;
|
|
648
|
+
}) => PageDependenciesResult | undefined | Promise<PageDependenciesResult | undefined>;
|
|
649
|
+
|
|
584
650
|
// Shared base option shape used by component(), html(), and layout()
|
|
585
651
|
interface ComponentOptions<P, E = EcoPagesElement> {
|
|
586
652
|
componentDir?: string;
|
|
@@ -595,7 +661,7 @@ type LayoutOptions<E = EcoPagesElement> = ComponentOptions<{ children: E }, E>;
|
|
|
595
661
|
|
|
596
662
|
interface PageOptions<T, E = EcoPagesElement> {
|
|
597
663
|
componentDir?: string;
|
|
598
|
-
dependencies?: EcoComponentDependencies
|
|
664
|
+
dependencies?: EcoComponentDependencies | GetPageDependencies<T>;
|
|
599
665
|
layout?: EcoComponent<{ children: E }>;
|
|
600
666
|
staticPaths?: GetStaticPaths;
|
|
601
667
|
staticProps?: GetStaticProps<T>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EcoComponent, EcoComponentConfig } from '../types/public-types.js';
|
|
2
|
+
import { type DiscoveredDependencies } from './discovered-dependencies.js';
|
|
2
3
|
/** Stable attribution for a Component created by an Integration-owned module. */
|
|
3
4
|
export type ComponentIdentity = {
|
|
4
5
|
id: string;
|
|
@@ -7,7 +8,7 @@ export type ComponentIdentity = {
|
|
|
7
8
|
};
|
|
8
9
|
/** Gets canonical component attribution. */
|
|
9
10
|
export declare function getComponentIdentity(value: EcoComponent | EcoComponentConfig | undefined): ComponentIdentity | undefined;
|
|
10
|
-
/** Merges attribution into factory options. */
|
|
11
|
-
export declare function bindComponentIdentity<T extends object>(identity: ComponentIdentity, options: T): T & {
|
|
11
|
+
/** Merges attribution into factory options. Optional discovered stylesheets stay keyed by identity. */
|
|
12
|
+
export declare function bindComponentIdentity<T extends object>(identity: ComponentIdentity, options: T, discovered?: DiscoveredDependencies): T & {
|
|
12
13
|
identity: ComponentIdentity;
|
|
13
14
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { registerDiscoveredDependencies } from './discovered-dependencies.js';
|
|
1
2
|
function asConfig(value) {
|
|
2
3
|
if (!value)
|
|
3
4
|
return undefined;
|
|
@@ -12,7 +13,9 @@ export function getComponentIdentity(value) {
|
|
|
12
13
|
const config = asConfig(value);
|
|
13
14
|
return config?.identity;
|
|
14
15
|
}
|
|
15
|
-
/** Merges attribution into factory options. */
|
|
16
|
-
export function bindComponentIdentity(identity, options) {
|
|
16
|
+
/** Merges attribution into factory options. Optional discovered stylesheets stay keyed by identity. */
|
|
17
|
+
export function bindComponentIdentity(identity, options, discovered) {
|
|
18
|
+
if (discovered)
|
|
19
|
+
registerDiscoveredDependencies(identity, discovered);
|
|
17
20
|
return { ...options, identity };
|
|
18
21
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { EcoComponentConfig } from '../types/public-types.js';
|
|
2
|
+
import type { ComponentIdentity } from './component-identity.js';
|
|
3
|
+
export type DiscoveredDependencies = {
|
|
4
|
+
components: () => unknown[];
|
|
5
|
+
stylesheets: string[];
|
|
6
|
+
watchFiles?: string[];
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Records discovered Components, inferred styles, and barrel watch hops on identity.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* Styles and watch hops are stored on the identity object through interned symbols so
|
|
13
|
+
* a bundled copy of this module still sees them during collection. The component getter
|
|
14
|
+
* stays on a same-isolate WeakMap and is attached to `config.dependencies` at factory time.
|
|
15
|
+
*/
|
|
16
|
+
export declare function registerDiscoveredDependencies(identity: ComponentIdentity, discovered: DiscoveredDependencies): void;
|
|
17
|
+
/**
|
|
18
|
+
* Inferred stylesheet paths registered for this component identity.
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* Provenance stays on identity so copying or merging `config.dependencies` cannot
|
|
22
|
+
* turn inferred styles into explicit ones. Paths are stored on the identity object
|
|
23
|
+
* so collection still sees them when server modules bundle a separate copy of `eco`.
|
|
24
|
+
* The collector prefers explicit entries across the whole graph by resolved path.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getInferredStylesheets(config: EcoComponentConfig | undefined): readonly string[];
|
|
27
|
+
/**
|
|
28
|
+
* Named re-export hops recorded for this component identity.
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* Barrel files are not Component identity files. Collecting them as watch paths
|
|
32
|
+
* lets a barrel retarget invalidate HTML and Page Browser Graph caches without
|
|
33
|
+
* editing the importing Page.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getDiscoveredWatchFiles(config: EcoComponentConfig | undefined): readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* Attaches deferred discovered Component references to `config.dependencies`.
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
* The component getter reads live import bindings only during graph traversal,
|
|
41
|
+
* after module initialization. Inferred stylesheets stay keyed by identity and are
|
|
42
|
+
* not copied into the public bag, so rendering, SSR preload, ownership, and watch
|
|
43
|
+
* collectors share the same graph without object-identity bookkeeping.
|
|
44
|
+
*/
|
|
45
|
+
export declare function attachDiscoveredDependencies(config: EcoComponentConfig): void;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const discoveries = new WeakMap();
|
|
2
|
+
const INFERRED_STYLESHEETS = Symbol.for('ecopages.identity.inferredStylesheets');
|
|
3
|
+
const DISCOVERED_WATCH_FILES = Symbol.for('ecopages.identity.watchFiles');
|
|
4
|
+
function discoveryRecord(identity) {
|
|
5
|
+
return identity;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Records discovered Components, inferred styles, and barrel watch hops on identity.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Styles and watch hops are stored on the identity object through interned symbols so
|
|
12
|
+
* a bundled copy of this module still sees them during collection. The component getter
|
|
13
|
+
* stays on a same-isolate WeakMap and is attached to `config.dependencies` at factory time.
|
|
14
|
+
*/
|
|
15
|
+
export function registerDiscoveredDependencies(identity, discovered) {
|
|
16
|
+
discoveries.set(identity, discovered);
|
|
17
|
+
const record = discoveryRecord(identity);
|
|
18
|
+
record[INFERRED_STYLESHEETS] = discovered.stylesheets;
|
|
19
|
+
record[DISCOVERED_WATCH_FILES] = discovered.watchFiles;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Inferred stylesheet paths registered for this component identity.
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* Provenance stays on identity so copying or merging `config.dependencies` cannot
|
|
26
|
+
* turn inferred styles into explicit ones. Paths are stored on the identity object
|
|
27
|
+
* so collection still sees them when server modules bundle a separate copy of `eco`.
|
|
28
|
+
* The collector prefers explicit entries across the whole graph by resolved path.
|
|
29
|
+
*/
|
|
30
|
+
export function getInferredStylesheets(config) {
|
|
31
|
+
const identity = config?.identity;
|
|
32
|
+
if (!identity)
|
|
33
|
+
return [];
|
|
34
|
+
return discoveryRecord(identity)[INFERRED_STYLESHEETS] ?? discoveries.get(identity)?.stylesheets ?? [];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Named re-export hops recorded for this component identity.
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
* Barrel files are not Component identity files. Collecting them as watch paths
|
|
41
|
+
* lets a barrel retarget invalidate HTML and Page Browser Graph caches without
|
|
42
|
+
* editing the importing Page.
|
|
43
|
+
*/
|
|
44
|
+
export function getDiscoveredWatchFiles(config) {
|
|
45
|
+
const identity = config?.identity;
|
|
46
|
+
if (!identity)
|
|
47
|
+
return [];
|
|
48
|
+
return discoveryRecord(identity)[DISCOVERED_WATCH_FILES] ?? discoveries.get(identity)?.watchFiles ?? [];
|
|
49
|
+
}
|
|
50
|
+
function isDeclared(value) {
|
|
51
|
+
return typeof value === 'function' && Boolean(value.config?.identity);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Attaches deferred discovered Component references to `config.dependencies`.
|
|
55
|
+
*
|
|
56
|
+
* @remarks
|
|
57
|
+
* The component getter reads live import bindings only during graph traversal,
|
|
58
|
+
* after module initialization. Inferred stylesheets stay keyed by identity and are
|
|
59
|
+
* not copied into the public bag, so rendering, SSR preload, ownership, and watch
|
|
60
|
+
* collectors share the same graph without object-identity bookkeeping.
|
|
61
|
+
*/
|
|
62
|
+
export function attachDiscoveredDependencies(config) {
|
|
63
|
+
const discovery = config.identity && discoveries.get(config.identity);
|
|
64
|
+
if (!discovery)
|
|
65
|
+
return;
|
|
66
|
+
let explicit = config.dependencies;
|
|
67
|
+
let merged;
|
|
68
|
+
const merge = () => {
|
|
69
|
+
const current = explicit;
|
|
70
|
+
merged = {
|
|
71
|
+
...current,
|
|
72
|
+
get components() {
|
|
73
|
+
return [...new Set([...(current?.components ?? []), ...discovery.components().filter(isDeclared)])];
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
merge();
|
|
78
|
+
Object.defineProperty(config, 'dependencies', {
|
|
79
|
+
enumerable: true,
|
|
80
|
+
configurable: true,
|
|
81
|
+
get: () => merged,
|
|
82
|
+
set: (value) => {
|
|
83
|
+
explicit = value;
|
|
84
|
+
merge();
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
package/src/eco/eco.js
CHANGED
|
@@ -6,6 +6,7 @@ import { finalizeComponentRender, getComponentRenderContext, interceptForeignChi
|
|
|
6
6
|
import { isThenable } from '../route-renderer/orchestration/foreign-child/foreign-child-output.utils.js';
|
|
7
7
|
import { applyPageLayoutConfig, mergeLayoutDependencies, normalizePageLayouts } from './page-layout-normalization.js';
|
|
8
8
|
import { getComponentIdentity } from './component-identity.js';
|
|
9
|
+
import { attachDiscoveredDependencies } from './discovered-dependencies.js';
|
|
9
10
|
/**
|
|
10
11
|
* Creates a component factory with lazy-trigger support and foreign-child-runtime
|
|
11
12
|
* interception.
|
|
@@ -58,6 +59,7 @@ function createComponentFactory(options) {
|
|
|
58
59
|
integration: options.integration,
|
|
59
60
|
dependencies: options.dependencies,
|
|
60
61
|
};
|
|
62
|
+
attachDiscoveredDependencies(comp.config);
|
|
61
63
|
return comp;
|
|
62
64
|
}
|
|
63
65
|
/**
|
package/src/eco/eco.types.d.ts
CHANGED
|
@@ -64,11 +64,27 @@ export type GetPageDependenciesContext<T = Record<string, unknown>> = {
|
|
|
64
64
|
query?: PageQuery;
|
|
65
65
|
};
|
|
66
66
|
/**
|
|
67
|
-
*
|
|
67
|
+
* Explicit dependency declarations owned by one file.
|
|
68
|
+
*
|
|
69
|
+
* @remarks
|
|
70
|
+
* Relative `scripts`, `stylesheets`, and `modules` resolve against `ownerFile`
|
|
71
|
+
* when present. Component entries keep their own identity files. Inferred
|
|
72
|
+
* stylesheets are not stored here; the collector reads them from identity.
|
|
68
73
|
*/
|
|
69
|
-
export type
|
|
74
|
+
export type FileOwnedDependencyContribution = EcoComponentDependencies & {
|
|
70
75
|
ownerFile?: string;
|
|
71
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* Page dependency bag made of one or more file-owned contributions.
|
|
79
|
+
*
|
|
80
|
+
* @remarks
|
|
81
|
+
* A single bag with `ownerFile` is one contribution. `contributions` keeps
|
|
82
|
+
* assets from different files separate until collection, so merging a Page and
|
|
83
|
+
* a content entry cannot re-home relative paths.
|
|
84
|
+
*/
|
|
85
|
+
export type PageDependenciesResult = FileOwnedDependencyContribution & {
|
|
86
|
+
contributions?: FileOwnedDependencyContribution[];
|
|
87
|
+
};
|
|
72
88
|
/**
|
|
73
89
|
* Resolves request-specific page dependencies for one route render.
|
|
74
90
|
*/
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { EcoComponentDependencies, FileOwnedDependencyContribution, PageDependenciesResult } from '../types/public-types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Expands a page dependency result into file-owned contributions.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Nested `contributions` are expanded so collection can resolve each bag against
|
|
7
|
+
* its own owner. Empty bags are omitted.
|
|
8
|
+
*/
|
|
9
|
+
export declare function listFileOwnedDependencyContributions(value: PageDependenciesResult | EcoComponentDependencies | undefined): FileOwnedDependencyContribution[];
|
|
10
|
+
/**
|
|
11
|
+
* Merges base page dependencies with additional dependencies (such as those returned by a content entry).
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* Keeps each file-owned contribution intact, including `modules`. Relative assets
|
|
15
|
+
* are not flattened under a single `ownerFile`, so a Page's `./page.css` cannot
|
|
16
|
+
* resolve against a content entry directory. Object-spread of two bags copies
|
|
17
|
+
* `ownerFile` onto the other side's relative paths; use this helper instead.
|
|
18
|
+
*/
|
|
19
|
+
export declare function mergePageDependencies(base: EcoComponentDependencies | undefined, additional: PageDependenciesResult | EcoComponentDependencies | undefined): PageDependenciesResult | undefined;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
function hasCollectableDependencyFields(dependencies) {
|
|
2
|
+
return Boolean(dependencies.stylesheets?.length ||
|
|
3
|
+
dependencies.scripts?.length ||
|
|
4
|
+
dependencies.modules?.length ||
|
|
5
|
+
dependencies.components?.length);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Expands a page dependency result into file-owned contributions.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Nested `contributions` are expanded so collection can resolve each bag against
|
|
12
|
+
* its own owner. Empty bags are omitted.
|
|
13
|
+
*/
|
|
14
|
+
export function listFileOwnedDependencyContributions(value) {
|
|
15
|
+
if (!value)
|
|
16
|
+
return [];
|
|
17
|
+
const result = value;
|
|
18
|
+
const nested = result.contributions ?? [];
|
|
19
|
+
const { contributions: _contributions, ownerFile, ...dependencies } = result;
|
|
20
|
+
const listed = [];
|
|
21
|
+
if (hasCollectableDependencyFields(dependencies)) {
|
|
22
|
+
listed.push(ownerFile ? { ...dependencies, ownerFile } : dependencies);
|
|
23
|
+
}
|
|
24
|
+
for (const contribution of nested) {
|
|
25
|
+
listed.push(...listFileOwnedDependencyContributions(contribution));
|
|
26
|
+
}
|
|
27
|
+
return listed;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Merges base page dependencies with additional dependencies (such as those returned by a content entry).
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* Keeps each file-owned contribution intact, including `modules`. Relative assets
|
|
34
|
+
* are not flattened under a single `ownerFile`, so a Page's `./page.css` cannot
|
|
35
|
+
* resolve against a content entry directory. Object-spread of two bags copies
|
|
36
|
+
* `ownerFile` onto the other side's relative paths; use this helper instead.
|
|
37
|
+
*/
|
|
38
|
+
export function mergePageDependencies(base, additional) {
|
|
39
|
+
if (!base)
|
|
40
|
+
return additional;
|
|
41
|
+
if (!additional)
|
|
42
|
+
return base;
|
|
43
|
+
const contributions = [
|
|
44
|
+
...listFileOwnedDependencyContributions(base),
|
|
45
|
+
...listFileOwnedDependencyContributions(additional),
|
|
46
|
+
];
|
|
47
|
+
if (contributions.length === 0)
|
|
48
|
+
return undefined;
|
|
49
|
+
if (contributions.length === 1)
|
|
50
|
+
return contributions[0];
|
|
51
|
+
return { contributions };
|
|
52
|
+
}
|
package/src/index.browser.d.ts
CHANGED
|
@@ -2,3 +2,6 @@ export type * from './types/public-types.js';
|
|
|
2
2
|
export type * from './eco/eco.types.js';
|
|
3
3
|
export { eco } from './eco/eco.browser.js';
|
|
4
4
|
export { bindComponentIdentity, getComponentIdentity, type ComponentIdentity } from './eco/component-identity.js';
|
|
5
|
+
export { attachDiscoveredDependencies } from './eco/discovered-dependencies.js';
|
|
6
|
+
export { listFileOwnedDependencyContributions, mergePageDependencies } from './eco/page-dependency-contributions.js';
|
|
7
|
+
export { mergeLayoutDependencies } from './eco/page-layout-normalization.js';
|
package/src/index.browser.js
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { eco } from './eco/eco.browser.js';
|
|
2
2
|
export { bindComponentIdentity, getComponentIdentity } from './eco/component-identity.js';
|
|
3
|
+
export { attachDiscoveredDependencies } from './eco/discovered-dependencies.js';
|
|
4
|
+
export { listFileOwnedDependencyContributions, mergePageDependencies } from './eco/page-dependency-contributions.js';
|
|
5
|
+
export { mergeLayoutDependencies } from './eco/page-layout-normalization.js';
|
package/src/index.d.ts
CHANGED
|
@@ -3,7 +3,10 @@ export type * from './eco/eco.types.js';
|
|
|
3
3
|
export { eco } from './eco/eco.js';
|
|
4
4
|
export { defineApiHandler, defineGroupHandler, defineGet, definePost, definePut, defineDelete, definePatch, defineOptions, defineHead, json, html, redirect, type GroupHandler, } from './adapters/shared/http/define-api-handler.js';
|
|
5
5
|
export { createEcoBuildPluginFromSourceTransform, createVitePluginsFromAppSourceTransforms, getAppSourceTransforms, createVitePluginFromSourceTransform, normalizeTransformId, type EcoSourceTransform, type EcoSourceTransformResult, type EcoViteCompatiblePlugin, } from './plugins/source-transform.js';
|
|
6
|
-
export { createEcoComponentMetaTransform } from './plugins/eco-component-meta-plugin.js';
|
|
6
|
+
export { attributeComponentIdentity, attributeMdxComponentIdentity, createEcoComponentMetaTransform, } from './plugins/eco-component-meta-plugin.js';
|
|
7
7
|
export { bindComponentIdentity, getComponentIdentity, type ComponentIdentity } from './eco/component-identity.js';
|
|
8
|
+
export { attachDiscoveredDependencies, registerDiscoveredDependencies, getInferredStylesheets, type DiscoveredDependencies, } from './eco/discovered-dependencies.js';
|
|
9
|
+
export { listFileOwnedDependencyContributions, mergePageDependencies } from './eco/page-dependency-contributions.js';
|
|
10
|
+
export { mergeLayoutDependencies } from './eco/page-layout-normalization.js';
|
|
8
11
|
export { SchemaError, validateStandardSchema } from './services/validation/validate-standard-schema.js';
|
|
9
12
|
export { buildIslandHostAttributes, ECO_ISLAND_HOST_ATTRIBUTE, ECO_ISLAND_INTEGRATION_ATTRIBUTE, finalizeIslandComponentRender, isIslandHostElement, mergeIslandHostAttributes, } from './islands/island-host.js';
|