@ecopages/core 0.2.0-rc.4 → 0.2.0-rc.6
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 +9 -3
- 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 +8 -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/processor.d.ts +4 -0
- package/src/plugins/processor.js +4 -0
- 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/invalidation/development-invalidation.service.js +10 -3
- package/src/services/module-loading/README.md +2 -0
- package/src/services/module-loading/collection-server-module-build.service.d.ts +1 -0
- package/src/services/module-loading/collection-server-module-build.service.js +5 -2
- 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/src/watchers/project-watcher.js +5 -1
|
@@ -15,7 +15,7 @@ import { RouteRenderOrchestrator, } from './route-pipeline/route-render-orchestr
|
|
|
15
15
|
import { createIntegrationRouteRenderAdapter } from './route-pipeline/integration-route-render-adapter.js';
|
|
16
16
|
import { createPageDependencyInstanceKey } from './page-browser-graph/route-instance-key.js';
|
|
17
17
|
import { mergePageBrowserGraphContributions } from './page-browser-graph/page-browser-graph-contribution.merge.js';
|
|
18
|
-
import { collectDependencyWatchPaths
|
|
18
|
+
import { collectDependencyWatchPaths } from '../page-loading/file-scoped-dependency-components.js';
|
|
19
19
|
import { resolvePageDependenciesFromContext, } from '../page-loading/resolved-page-dependencies.js';
|
|
20
20
|
import { createGroupedGraphBuildPlanKey, } from './page-browser-graph/grouped-graph-build-plan.js';
|
|
21
21
|
import { normalizeUnresolvedMarkerArtifactHtml } from './route-pipeline/marker-artifact.utils.js';
|
|
@@ -68,6 +68,10 @@ export class IntegrationRenderer {
|
|
|
68
68
|
integrationName: this.name,
|
|
69
69
|
runtimeOrigin: this.runtimeOrigin,
|
|
70
70
|
});
|
|
71
|
+
const integration = this.appConfig.integrations?.find((entry) => entry.name === this.name);
|
|
72
|
+
if (integration?.getResolvedIntegrationDependencies) {
|
|
73
|
+
this.resolvedIntegrationDependencies = integration.getResolvedIntegrationDependencies();
|
|
74
|
+
}
|
|
71
75
|
}
|
|
72
76
|
/**
|
|
73
77
|
* Prebuilds the production Page Browser Graph for one route file and optional params.
|
|
@@ -586,19 +590,14 @@ export class IntegrationRenderer {
|
|
|
586
590
|
};
|
|
587
591
|
}
|
|
588
592
|
async resolvePageDependencies(context) {
|
|
589
|
-
return resolvePageDependenciesFromContext(context, this.name, (
|
|
593
|
+
return resolvePageDependenciesFromContext(context, this.name, (components, ownerFile) => this.resolvePageBrowserGraphContributionFromComponents(components, ownerFile));
|
|
590
594
|
}
|
|
591
|
-
async
|
|
592
|
-
const components = collectFileScopedDependencyComponents({
|
|
593
|
-
ownerFile,
|
|
594
|
-
integrationName: this.name,
|
|
595
|
-
dependencies,
|
|
596
|
-
});
|
|
595
|
+
async resolvePageBrowserGraphContributionFromComponents(components, ownerFile) {
|
|
597
596
|
if (components.length === 0) {
|
|
598
597
|
return undefined;
|
|
599
598
|
}
|
|
600
599
|
return {
|
|
601
|
-
assets: await this.processComponentDependencies(components),
|
|
600
|
+
assets: await this.processComponentDependencies([...components]),
|
|
602
601
|
watchPaths: collectDependencyWatchPaths(ownerFile, components),
|
|
603
602
|
};
|
|
604
603
|
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
|
|
2
2
|
import type { EcoComponent, ResolvedLazyTrigger } from '../../../types/public-types.js';
|
|
3
|
-
import type {
|
|
4
|
-
import type { AssetProcessingService, ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
|
|
3
|
+
import type { ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
|
|
5
4
|
export declare function collectIntegrationNamesFromGraph(components: (EcoComponent | Partial<EcoComponent>)[], currentIntegrationName: string): Set<string>;
|
|
6
5
|
export declare function collectResolvedLazyTriggersFromGraph(components: (EcoComponent | Partial<EcoComponent>)[], currentIntegrationName: string): ResolvedLazyTrigger[];
|
|
7
6
|
export declare function hasForeignChildDescendantsInGraph(component: EcoComponent, currentIntegrationName: string): boolean;
|
|
8
|
-
export declare function collectEagerSsrLazyScriptDefinitions(components: (EcoComponent | Partial<EcoComponent>)[]): AssetDefinition[];
|
|
9
7
|
export declare function collectUsedIntegrationDependenciesFromGraph(appConfig: EcoPagesAppConfig, components: (EcoComponent | Partial<EcoComponent>)[], currentIntegrationName: string): ProcessedAsset[];
|
|
10
|
-
export declare function buildEagerSsrLazyAssetsFromGraph(assetProcessingService: AssetProcessingService, components: (EcoComponent | Partial<EcoComponent>)[], currentIntegrationName: string): Promise<ProcessedAsset[]>;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { AssetFactory } from '../../../services/assets/asset-processing-service/index.js';
|
|
3
1
|
import { walkComponentGraph } from './component-graph.js';
|
|
4
2
|
import { getComponentIdentity } from '../../../eco/component-identity.js';
|
|
5
3
|
function toGraphRoots(components) {
|
|
@@ -51,63 +49,6 @@ export function hasForeignChildDescendantsInGraph(component, currentIntegrationN
|
|
|
51
49
|
});
|
|
52
50
|
return foundForeign;
|
|
53
51
|
}
|
|
54
|
-
export function collectEagerSsrLazyScriptDefinitions(components) {
|
|
55
|
-
const dependencies = [];
|
|
56
|
-
const seenKeys = new Set();
|
|
57
|
-
const normalizeAttributes = (attributes) => ({
|
|
58
|
-
type: 'module',
|
|
59
|
-
defer: '',
|
|
60
|
-
...(attributes ?? {}),
|
|
61
|
-
});
|
|
62
|
-
walkComponentGraph({
|
|
63
|
-
roots: toGraphRoots(components),
|
|
64
|
-
currentIntegrationName: '',
|
|
65
|
-
visitLayout: true,
|
|
66
|
-
onConfig: (config) => {
|
|
67
|
-
const componentFile = getComponentIdentity(config)?.file;
|
|
68
|
-
if (!componentFile || !config) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
const componentDir = path.dirname(componentFile);
|
|
72
|
-
for (const script of config.dependencies?.scripts ?? []) {
|
|
73
|
-
if (typeof script === 'string' || !script.lazy || script.ssr !== true) {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
const attributes = normalizeAttributes(script.attributes);
|
|
77
|
-
if (script.content) {
|
|
78
|
-
const key = `content:${script.content}:${JSON.stringify(attributes)}`;
|
|
79
|
-
if (seenKeys.has(key)) {
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
seenKeys.add(key);
|
|
83
|
-
dependencies.push(AssetFactory.createContentScript({
|
|
84
|
-
position: 'head',
|
|
85
|
-
content: script.content,
|
|
86
|
-
attributes,
|
|
87
|
-
packageRole: 'dynamic-chunk',
|
|
88
|
-
}));
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if (!script.src) {
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
const resolvedPath = path.resolve(componentDir, script.src);
|
|
95
|
-
const key = `file:${resolvedPath}:${JSON.stringify(attributes)}`;
|
|
96
|
-
if (seenKeys.has(key)) {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
seenKeys.add(key);
|
|
100
|
-
dependencies.push(AssetFactory.createFileScript({
|
|
101
|
-
filepath: resolvedPath,
|
|
102
|
-
position: 'head',
|
|
103
|
-
attributes,
|
|
104
|
-
packageRole: 'dynamic-chunk',
|
|
105
|
-
}));
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
});
|
|
109
|
-
return dependencies;
|
|
110
|
-
}
|
|
111
52
|
export function collectUsedIntegrationDependenciesFromGraph(appConfig, components, currentIntegrationName) {
|
|
112
53
|
const integrationNames = collectIntegrationNamesFromGraph(components, currentIntegrationName);
|
|
113
54
|
const dependencies = [];
|
|
@@ -123,10 +64,3 @@ export function collectUsedIntegrationDependenciesFromGraph(appConfig, component
|
|
|
123
64
|
}
|
|
124
65
|
return dependencies;
|
|
125
66
|
}
|
|
126
|
-
export async function buildEagerSsrLazyAssetsFromGraph(assetProcessingService, components, currentIntegrationName) {
|
|
127
|
-
const dependencies = collectEagerSsrLazyScriptDefinitions(components);
|
|
128
|
-
if (dependencies.length === 0) {
|
|
129
|
-
return [];
|
|
130
|
-
}
|
|
131
|
-
return assetProcessingService.processDependencies(dependencies, `${currentIntegrationName}:ssr-lazy`);
|
|
132
|
-
}
|
|
@@ -2,7 +2,7 @@ import { collectHtmlCacheSourceDependencyPaths } from '../../../services/cache/h
|
|
|
2
2
|
import { getComponentIdentity } from '../../../eco/component-identity.js';
|
|
3
3
|
import { inspectUnresolvedMarkerArtifactHtml } from './marker-artifact.utils.js';
|
|
4
4
|
import { OwnershipValidationService, throwIfOwnershipInvalid, } from '../ownership-graph/ownership-validation.service.js';
|
|
5
|
-
import {
|
|
5
|
+
import { collectResolvedLazyTriggersFromGraph, collectUsedIntegrationDependenciesFromGraph, } from '../ownership-graph/component-graph-collectors.js';
|
|
6
6
|
import { buildGlobalInjectorAssets } from '../page-browser-graph/global-injector-assets.service.js';
|
|
7
7
|
import { mergePageBrowserGraphContributions } from '../page-browser-graph/page-browser-graph-contribution.merge.js';
|
|
8
8
|
import { PageBrowserGraphService } from '../page-browser-graph/page-browser-graph.service.js';
|
|
@@ -93,13 +93,10 @@ export class RouteRenderOrchestrator {
|
|
|
93
93
|
...collectUsedIntegrationDependenciesFromGraph(this.appConfig, componentsToResolve, adapter.name),
|
|
94
94
|
];
|
|
95
95
|
const triggers = collectResolvedLazyTriggersFromGraph(componentsToResolve, adapter.name);
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
buildEagerSsrLazyAssetsFromGraph(this.assetProcessingService, componentsToResolve, adapter.name),
|
|
101
|
-
]);
|
|
102
|
-
allDependencies.push(...globalAssets, ...eagerSsrLazyAssets);
|
|
96
|
+
const globalAssets = triggers.length > 0
|
|
97
|
+
? await buildGlobalInjectorAssets(this.appConfig, this.assetProcessingService, triggers, adapter.name)
|
|
98
|
+
: [];
|
|
99
|
+
allDependencies.push(...globalAssets);
|
|
103
100
|
const sourceDependencyPaths = collectHtmlCacheSourceDependencyPaths({
|
|
104
101
|
routeFile: routeOptions.file,
|
|
105
102
|
processedAssets: allDependencies,
|
|
@@ -30,8 +30,12 @@ type CollectComponentDependenciesOptions = {
|
|
|
30
30
|
* Walks component dependency trees and collects declared assets, module declarations,
|
|
31
31
|
* and lazy-script group metadata in one pass.
|
|
32
32
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* Explicit stylesheet declarations are collected first across the graph. Inferred
|
|
35
|
+
* stylesheets are read from component identity afterward and skipped when the same
|
|
36
|
+
* resolved path already has an explicit entry. The returned structures preserve both
|
|
37
|
+
* the flat asset list used by asset processing and the grouped lazy-trigger data used
|
|
38
|
+
* later during render output.
|
|
35
39
|
*/
|
|
36
40
|
export declare function collectComponentDependencies(options: CollectComponentDependenciesOptions): CollectedComponentDependencies;
|
|
37
41
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { assertEcoDeclaredComponent } from '../../eco/eco-declared-component.js';
|
|
3
3
|
import { getComponentIdentity } from '../../eco/component-identity.js';
|
|
4
|
+
import { getInferredStylesheets } from '../../eco/discovered-dependencies.js';
|
|
4
5
|
import { AssetFactory } from '../../services/assets/asset-processing-service/index.js';
|
|
5
6
|
import { extractEcopagesVirtualImports } from './ecopages-virtual-imports.js';
|
|
6
7
|
import { collectDeclaredAssetEntries } from './declared-asset-collection.js';
|
|
@@ -27,20 +28,27 @@ function pushUniqueDependency(keys, key, dependencies, dependency) {
|
|
|
27
28
|
return true;
|
|
28
29
|
}
|
|
29
30
|
function resolveDependencyPath(componentDir, pathUrl) {
|
|
30
|
-
return path.join(componentDir, pathUrl);
|
|
31
|
+
return path.isAbsolute(pathUrl) ? pathUrl : path.join(componentDir, pathUrl);
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
34
|
* Walks component dependency trees and collects declared assets, module declarations,
|
|
34
35
|
* and lazy-script group metadata in one pass.
|
|
35
36
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* Explicit stylesheet declarations are collected first across the graph. Inferred
|
|
39
|
+
* stylesheets are read from component identity afterward and skipped when the same
|
|
40
|
+
* resolved path already has an explicit entry. The returned structures preserve both
|
|
41
|
+
* the flat asset list used by asset processing and the grouped lazy-trigger data used
|
|
42
|
+
* later during render output.
|
|
38
43
|
*/
|
|
39
44
|
export function collectComponentDependencies(options) {
|
|
40
45
|
const { components, integrationName, resolveLazyScripts, createEcopagesJsxLazyEntryName, isEcopagesJsxIntegration, errors, } = options;
|
|
41
46
|
const dependencies = [];
|
|
42
47
|
const lazyScriptsByConfig = new Map();
|
|
43
48
|
const lazyDependencyKeys = new Set();
|
|
49
|
+
const visited = new Set();
|
|
50
|
+
const inferredStyles = [];
|
|
51
|
+
const explicitStylePaths = new Set();
|
|
44
52
|
for (const component of components) {
|
|
45
53
|
if (!component)
|
|
46
54
|
continue;
|
|
@@ -54,13 +62,22 @@ export function collectComponentDependencies(options) {
|
|
|
54
62
|
* Recursively visits one component config and its declared child dependencies.
|
|
55
63
|
*/
|
|
56
64
|
const collect = (config) => {
|
|
57
|
-
if (!config)
|
|
65
|
+
if (!config || visited.has(config))
|
|
58
66
|
return;
|
|
67
|
+
visited.add(config);
|
|
59
68
|
const file = getComponentIdentity(config)?.file;
|
|
60
69
|
if (!file)
|
|
61
70
|
return;
|
|
62
71
|
const dir = path.dirname(file);
|
|
63
72
|
const dependenciesConfig = config.dependencies;
|
|
73
|
+
const explicitStyles = dependenciesConfig?.stylesheets ?? [];
|
|
74
|
+
for (const style of explicitStyles) {
|
|
75
|
+
const src = typeof style === 'string' ? style : style.src;
|
|
76
|
+
if (src)
|
|
77
|
+
explicitStylePaths.add(resolveDependencyPath(dir, src));
|
|
78
|
+
}
|
|
79
|
+
for (const src of getInferredStylesheets(config))
|
|
80
|
+
inferredStyles.push({ dir, src });
|
|
64
81
|
const registerLazyScript = ({ lazy, lazyKey, fallbackUrl, }) => {
|
|
65
82
|
let grouped = lazyScriptsByConfig.get(config);
|
|
66
83
|
if (!grouped) {
|
|
@@ -73,7 +90,7 @@ export function collectComponentDependencies(options) {
|
|
|
73
90
|
grouped.set(triggerKey, existing);
|
|
74
91
|
};
|
|
75
92
|
collectDeclaredAssetEntries({
|
|
76
|
-
stylesheetEntries:
|
|
93
|
+
stylesheetEntries: explicitStyles,
|
|
77
94
|
scriptEntries: dependenciesConfig?.scripts ?? [],
|
|
78
95
|
componentDir: dir,
|
|
79
96
|
dependencies,
|
|
@@ -125,6 +142,13 @@ export function collectComponentDependencies(options) {
|
|
|
125
142
|
});
|
|
126
143
|
}));
|
|
127
144
|
}
|
|
145
|
+
for (const { dir, src } of inferredStyles) {
|
|
146
|
+
const filepath = resolveDependencyPath(dir, src);
|
|
147
|
+
if (explicitStylePaths.has(filepath))
|
|
148
|
+
continue;
|
|
149
|
+
explicitStylePaths.add(filepath);
|
|
150
|
+
dependencies.push(AssetFactory.createFileStylesheet({ filepath, position: 'head', attributes: { rel: 'stylesheet' } }));
|
|
151
|
+
}
|
|
128
152
|
return {
|
|
129
153
|
dependencies,
|
|
130
154
|
lazyScriptsByConfig,
|
|
@@ -21,12 +21,21 @@ export declare function collectFileScopedDependencyComponents(options: {
|
|
|
21
21
|
integrationName: string;
|
|
22
22
|
dependencies: EcoComponentDependencies;
|
|
23
23
|
}): Array<EcoComponent | Partial<EcoComponent>>;
|
|
24
|
+
/**
|
|
25
|
+
* Expands a page dependency result into collector roots, retaining each contribution's owner.
|
|
26
|
+
*/
|
|
27
|
+
export declare function collectPageDependencyComponents(options: {
|
|
28
|
+
result: PageDependenciesResult;
|
|
29
|
+
fallbackOwnerFile: string;
|
|
30
|
+
integrationName: string;
|
|
31
|
+
}): Array<EcoComponent | Partial<EcoComponent>>;
|
|
24
32
|
export type CollectComponentConfigFilePathsOptions = {
|
|
25
33
|
additionalPaths?: ReadonlyArray<string>;
|
|
26
34
|
includeLayouts?: boolean;
|
|
35
|
+
includeStylesheets?: boolean;
|
|
27
36
|
};
|
|
28
37
|
/**
|
|
29
|
-
* Walks component configs and collects
|
|
38
|
+
* Walks component configs and collects identity files, stylesheets, and discovered barrel hops.
|
|
30
39
|
*/
|
|
31
40
|
export declare function collectComponentConfigFilePaths(components: ReadonlyArray<EcoComponent | Partial<EcoComponent> | undefined>, options?: CollectComponentConfigFilePathsOptions): Set<string>;
|
|
32
41
|
/**
|
|
@@ -39,4 +48,5 @@ export declare function collectDependencyWatchPaths(ownerFile: string, component
|
|
|
39
48
|
export declare function splitPageDependenciesResult(result: PageDependenciesResult): {
|
|
40
49
|
dependencies: EcoComponentDependencies;
|
|
41
50
|
ownerFile?: string;
|
|
51
|
+
contributions?: PageDependenciesResult['contributions'];
|
|
42
52
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { rapidhash } from '../../utils/hash.js';
|
|
3
3
|
import { bindComponentIdentity, getComponentIdentity } from '../../eco/component-identity.js';
|
|
4
|
+
import { getDiscoveredWatchFiles, getInferredStylesheets } from '../../eco/discovered-dependencies.js';
|
|
5
|
+
import { listFileOwnedDependencyContributions } from '../../eco/page-dependency-contributions.js';
|
|
4
6
|
/**
|
|
5
7
|
* Attaches canonical file-backed identity to one component config.
|
|
6
8
|
*/
|
|
@@ -43,7 +45,22 @@ export function collectFileScopedDependencyComponents(options) {
|
|
|
43
45
|
return components;
|
|
44
46
|
}
|
|
45
47
|
/**
|
|
46
|
-
*
|
|
48
|
+
* Expands a page dependency result into collector roots, retaining each contribution's owner.
|
|
49
|
+
*/
|
|
50
|
+
export function collectPageDependencyComponents(options) {
|
|
51
|
+
const components = [];
|
|
52
|
+
for (const contribution of listFileOwnedDependencyContributions(options.result)) {
|
|
53
|
+
const { ownerFile, ...dependencies } = contribution;
|
|
54
|
+
components.push(...collectFileScopedDependencyComponents({
|
|
55
|
+
ownerFile: ownerFile ?? options.fallbackOwnerFile,
|
|
56
|
+
integrationName: options.integrationName,
|
|
57
|
+
dependencies,
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
return components;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Walks component configs and collects identity files, stylesheets, and discovered barrel hops.
|
|
47
64
|
*/
|
|
48
65
|
export function collectComponentConfigFilePaths(components, options) {
|
|
49
66
|
const files = new Set();
|
|
@@ -60,11 +77,24 @@ export function collectComponentConfigFilePaths(components, options) {
|
|
|
60
77
|
return;
|
|
61
78
|
}
|
|
62
79
|
const resolved = path.resolve(file);
|
|
63
|
-
if (visited.has(
|
|
80
|
+
if (visited.has(config)) {
|
|
64
81
|
return;
|
|
65
82
|
}
|
|
66
|
-
visited.add(
|
|
83
|
+
visited.add(config);
|
|
67
84
|
files.add(resolved);
|
|
85
|
+
for (const style of options?.includeStylesheets ? (config.dependencies?.stylesheets ?? []) : []) {
|
|
86
|
+
const src = typeof style === 'string' ? style : style.src;
|
|
87
|
+
if (src)
|
|
88
|
+
files.add(path.resolve(path.dirname(resolved), src));
|
|
89
|
+
}
|
|
90
|
+
if (options?.includeStylesheets) {
|
|
91
|
+
for (const src of getInferredStylesheets(config)) {
|
|
92
|
+
files.add(path.resolve(path.dirname(resolved), src));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const watchFile of getDiscoveredWatchFiles(config)) {
|
|
96
|
+
files.add(path.resolve(watchFile));
|
|
97
|
+
}
|
|
68
98
|
for (const dependency of config.dependencies?.components ?? []) {
|
|
69
99
|
visit(dependency?.config);
|
|
70
100
|
}
|
|
@@ -83,12 +113,16 @@ export function collectComponentConfigFilePaths(components, options) {
|
|
|
83
113
|
* Collects source files that should invalidate a per-instance browser graph entry.
|
|
84
114
|
*/
|
|
85
115
|
export function collectDependencyWatchPaths(ownerFile, components) {
|
|
86
|
-
return Array.from(collectComponentConfigFilePaths(components, { additionalPaths: [ownerFile] }));
|
|
116
|
+
return Array.from(collectComponentConfigFilePaths(components, { additionalPaths: [ownerFile], includeStylesheets: true }));
|
|
87
117
|
}
|
|
88
118
|
/**
|
|
89
119
|
* Separates optional owner metadata from a resolved page dependency bag.
|
|
90
120
|
*/
|
|
91
121
|
export function splitPageDependenciesResult(result) {
|
|
92
|
-
const { ownerFile, ...dependencies } = result;
|
|
93
|
-
return {
|
|
122
|
+
const { ownerFile, contributions, ...dependencies } = result;
|
|
123
|
+
return {
|
|
124
|
+
dependencies,
|
|
125
|
+
ownerFile,
|
|
126
|
+
...(contributions ? { contributions } : {}),
|
|
127
|
+
};
|
|
94
128
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { EcoComponent, PageBrowserGraphContribution, PageBrowserGraphContributionContext } from '../../types/public-types.js';
|
|
2
|
-
import { collectFileScopedDependencyComponents } from './file-scoped-dependency-components.js';
|
|
3
2
|
export type ResolvedPageDependencies = {
|
|
4
3
|
ownerFile: string;
|
|
5
4
|
components: ReadonlyArray<EcoComponent | Partial<EcoComponent>>;
|
|
6
5
|
contribution?: PageBrowserGraphContribution;
|
|
7
6
|
};
|
|
8
|
-
export type ResolvePageDependenciesContribution = (
|
|
7
|
+
export type ResolvePageDependenciesContribution = (components: ReadonlyArray<EcoComponent | Partial<EcoComponent>>, ownerFile: string) => Promise<PageBrowserGraphContribution | undefined>;
|
|
9
8
|
/**
|
|
10
9
|
* Resolves `eco.page().dependencies()` once for one graph contribution context.
|
|
11
10
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { listFileOwnedDependencyContributions } from '../../eco/page-dependency-contributions.js';
|
|
2
|
+
import { collectPageDependencyComponents } from './file-scoped-dependency-components.js';
|
|
2
3
|
/**
|
|
3
4
|
* Resolves `eco.page().dependencies()` once for one graph contribution context.
|
|
4
5
|
*/
|
|
@@ -16,12 +17,12 @@ export async function resolvePageDependenciesFromContext(context, integrationNam
|
|
|
16
17
|
if (!dependenciesResult) {
|
|
17
18
|
return undefined;
|
|
18
19
|
}
|
|
19
|
-
const
|
|
20
|
-
const resolvedOwnerFile = ownerFile ?? context.file;
|
|
21
|
-
const components =
|
|
22
|
-
|
|
20
|
+
const contributions = listFileOwnedDependencyContributions(dependenciesResult);
|
|
21
|
+
const resolvedOwnerFile = contributions.length === 1 ? (contributions[0]?.ownerFile ?? context.file) : context.file;
|
|
22
|
+
const components = collectPageDependencyComponents({
|
|
23
|
+
result: dependenciesResult,
|
|
24
|
+
fallbackOwnerFile: context.file,
|
|
23
25
|
integrationName,
|
|
24
|
-
dependencies,
|
|
25
26
|
});
|
|
26
27
|
if (components.length === 0) {
|
|
27
28
|
return {
|
|
@@ -29,7 +30,7 @@ export async function resolvePageDependenciesFromContext(context, integrationNam
|
|
|
29
30
|
components,
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
|
-
const contribution = await materializeContribution(
|
|
33
|
+
const contribution = await materializeContribution(components, resolvedOwnerFile);
|
|
33
34
|
return {
|
|
34
35
|
ownerFile: resolvedOwnerFile,
|
|
35
36
|
components,
|
|
@@ -2,6 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { getAppServerInvalidationState } from '../runtime-state/server-invalidation-state.service.js';
|
|
3
3
|
import { appLogger } from '../../global/app-logger.js';
|
|
4
4
|
import { clearAppDevelopmentRouteModuleBuildCaches } from '../module-loading/route-module-build-cache-registry.js';
|
|
5
|
+
import { clearCollectionServerBuildArtifacts } from '../module-loading/collection-server-module-build.service.js';
|
|
5
6
|
/**
|
|
6
7
|
* Framework-owned development invalidation service.
|
|
7
8
|
*
|
|
@@ -31,6 +32,10 @@ export class DevelopmentInvalidationService {
|
|
|
31
32
|
getAppServerInvalidationState(this.appConfig).invalidateServerModules(changedFiles);
|
|
32
33
|
this.appConfig.runtime?.appModuleLoader?.invalidateDevelopmentGraph();
|
|
33
34
|
clearAppDevelopmentRouteModuleBuildCaches(this.appConfig);
|
|
35
|
+
clearCollectionServerBuildArtifacts(this.appConfig);
|
|
36
|
+
for (const processor of this.appConfig.processors.values()) {
|
|
37
|
+
processor.invalidateServerArtifacts?.();
|
|
38
|
+
}
|
|
34
39
|
}
|
|
35
40
|
/**
|
|
36
41
|
* Registers an integration-owned handler for registered script entrypoint edits.
|
|
@@ -80,7 +85,7 @@ export class DevelopmentInvalidationService {
|
|
|
80
85
|
if (this.matchesAdditionalWatchPaths(filePath)) {
|
|
81
86
|
return {
|
|
82
87
|
category: 'additional-watch',
|
|
83
|
-
invalidateServerModules:
|
|
88
|
+
invalidateServerModules: true,
|
|
84
89
|
refreshRoutes: false,
|
|
85
90
|
reloadBrowser: true,
|
|
86
91
|
delegateToHmr: false,
|
|
@@ -162,11 +167,13 @@ export class DevelopmentInvalidationService {
|
|
|
162
167
|
return false;
|
|
163
168
|
for (const pattern of patterns) {
|
|
164
169
|
if (pattern.includes('*')) {
|
|
165
|
-
const ext = pattern.replace(
|
|
170
|
+
const ext = pattern.replace(/\*\*?\/\*|\*+/g, '');
|
|
166
171
|
if (normalizedPath.endsWith(ext))
|
|
167
172
|
return true;
|
|
173
|
+
continue;
|
|
168
174
|
}
|
|
169
|
-
|
|
175
|
+
const resolvedPattern = path.isAbsolute(pattern) ? pattern : path.resolve(this.appConfig.rootDir, pattern);
|
|
176
|
+
if (normalizedPath === resolvedPattern || normalizedPath.startsWith(`${resolvedPattern}${path.sep}`)) {
|
|
170
177
|
return true;
|
|
171
178
|
}
|
|
172
179
|
}
|
|
@@ -35,6 +35,8 @@ Call site (route scan, renderer, SSG, API)
|
|
|
35
35
|
|
|
36
36
|
Development import URLs use `sourceHash` plus a per-service import generation counter. Node uses that value in its `?update=` query. Bun also receives a generation-specific compiled output filename because it retains a previously imported file when only its query changes. Both paths advance after `invalidateDevelopmentGraph()` without a process-wide invalidation version in reuse keys.
|
|
37
37
|
|
|
38
|
+
Compiled collection server modules also include the app-owned server invalidation version in their output filename (`<collection>-<buildIdentity>-<invalidationVersion>.mjs`) so Node's ESM loader treats recompiled bundles as new modules after co-located helper files change.
|
|
39
|
+
|
|
38
40
|
## Files
|
|
39
41
|
|
|
40
42
|
| File | Role |
|
|
@@ -5,6 +5,7 @@ export type CollectionServerBuildArtifact = {
|
|
|
5
5
|
outputUrl: string;
|
|
6
6
|
sourcePaths: string[];
|
|
7
7
|
buildIdentity: string;
|
|
8
|
+
invalidationVersion: number;
|
|
8
9
|
};
|
|
9
10
|
export declare function getCollectionServerBuildArtifact(appConfig: EcoPagesAppConfig, collectionName: string): CollectionServerBuildArtifact | undefined;
|
|
10
11
|
export declare function clearCollectionServerBuildArtifacts(appConfig: EcoPagesAppConfig): void;
|
|
@@ -7,6 +7,7 @@ import { createServerBuildRequest } from '../../build/runtime/build-request-poli
|
|
|
7
7
|
import { recordCollectionBuild } from '../../diagnostics/request-pipeline-metrics.js';
|
|
8
8
|
import { resolveInternalExecutionDir } from '../../utils/resolve-work-dir.js';
|
|
9
9
|
import { fileSystem } from '@ecopages/file-system';
|
|
10
|
+
import { getAppServerInvalidationState } from '../runtime-state/server-invalidation-state.service.js';
|
|
10
11
|
const artifactsByCollection = new WeakMap();
|
|
11
12
|
function getArtifactMap(appConfig) {
|
|
12
13
|
let artifacts = artifactsByCollection.get(appConfig);
|
|
@@ -51,13 +52,14 @@ export async function buildCollectionServerModule(input) {
|
|
|
51
52
|
splitting: false,
|
|
52
53
|
externalPackages: true,
|
|
53
54
|
});
|
|
55
|
+
const invalidationVersion = getAppServerInvalidationState(appConfig).getServerInvalidationVersion();
|
|
54
56
|
const buildIdentity = createCollectionBuildIdentity(createBuildRequestIdentity(buildRequest), sourceFilePath, ownedSourcePaths);
|
|
55
57
|
const artifacts = getArtifactMap(appConfig);
|
|
56
58
|
const existing = artifacts.get(collectionName);
|
|
57
|
-
if (existing?.buildIdentity === buildIdentity) {
|
|
59
|
+
if (existing?.buildIdentity === buildIdentity && existing?.invalidationVersion === invalidationVersion) {
|
|
58
60
|
return existing;
|
|
59
61
|
}
|
|
60
|
-
const outputFileName = `${collectionName}-${buildIdentity}.mjs`;
|
|
62
|
+
const outputFileName = `${collectionName}-${buildIdentity}-${invalidationVersion}.mjs`;
|
|
61
63
|
const buildOptions = { ...buildRequest, naming: outputFileName.replace(/\.mjs$/u, '.[ext]') };
|
|
62
64
|
recordCollectionBuild();
|
|
63
65
|
const buildResult = await build(buildOptions);
|
|
@@ -77,6 +79,7 @@ export async function buildCollectionServerModule(input) {
|
|
|
77
79
|
outputUrl: pathToFileURL(compiledOutput).href,
|
|
78
80
|
sourcePaths: [...ownedSourcePaths, sourceFilePath],
|
|
79
81
|
buildIdentity,
|
|
82
|
+
invalidationVersion,
|
|
80
83
|
};
|
|
81
84
|
artifacts.set(collectionName, artifact);
|
|
82
85
|
return artifact;
|
|
@@ -16,6 +16,8 @@ interface PageModuleImportBaseOptions {
|
|
|
16
16
|
export interface PageModuleBuildImportOptions extends PageModuleImportBaseOptions {
|
|
17
17
|
rootDir: string;
|
|
18
18
|
outdir: string;
|
|
19
|
+
/** Use the server build pipeline even when a development host can load source modules. */
|
|
20
|
+
forceBuild?: boolean;
|
|
19
21
|
buildExecutor?: BuildExecutor;
|
|
20
22
|
splitting?: boolean;
|
|
21
23
|
externalPackages?: boolean;
|
|
@@ -72,7 +72,8 @@ export class PageModuleImportService {
|
|
|
72
72
|
async importModule(options) {
|
|
73
73
|
const { filePath } = options;
|
|
74
74
|
const fileHash = this.dependencies.hashFile(filePath);
|
|
75
|
-
const hostModuleLoader =
|
|
75
|
+
const hostModuleLoader = !options.forceBuild &&
|
|
76
|
+
typeof Bun === 'undefined' &&
|
|
76
77
|
process.env.NODE_ENV === 'development' &&
|
|
77
78
|
this.dependencies.canLoadSourceModuleFromHost(filePath)
|
|
78
79
|
? this.dependencies.getHostModuleLoader()
|
|
@@ -58,10 +58,12 @@ Integrations may implement:
|
|
|
58
58
|
- `beforeStaticExport(context)` — runs after unified-graph prebuild, before page rendering
|
|
59
59
|
- `afterStaticExport(context)` — runs in a `finally` block after generation completes
|
|
60
60
|
|
|
61
|
-
When `appConfig.sitemap.enabled` is true, `sitemap.xml` is written **after** `afterStaticExport` so integration-generated URLs can be listed via `extraUrls`.
|
|
61
|
+
When `appConfig.sitemap.enabled` is true, `sitemap.xml` is written **after** `afterStaticExport` so integration-generated URLs can be listed via `extraUrls`. Development (`ecopages dev`) does not emit this file.
|
|
62
62
|
|
|
63
63
|
Sitemap eligibility is decided during successful page export (`activeStaticPathnames` plus `robots.index !== false`, fail-closed on metadata errors). `resolveSitemapLocations` then applies `exclude` and appends `extraUrls`.
|
|
64
64
|
|
|
65
65
|
`StaticExportContext.routes` is the unfiltered static-generation route list. Sitemap filtering is applied separately.
|
|
66
66
|
|
|
67
67
|
See `StaticExportContext` in `static-export-context.ts`.
|
|
68
|
+
|
|
69
|
+
User-facing guide: [Sitemap](https://github.com/ecopages/ecopages/blob/main/apps/docs/src/content/docs/core/sitemap.mdx) in the docs app.
|
|
@@ -11,7 +11,7 @@ import type { InteractionEventsString as ScriptsInjectorInteractionEventsString
|
|
|
11
11
|
import type { EntrypointDependencyGraph } from '../services/runtime-state/entrypoint-dependency-graph.service.js';
|
|
12
12
|
import type { DevTransformBundleContributor } from '../dev/transform-server/types.js';
|
|
13
13
|
export type { EcoPagesAppConfig } from './internal-types.js';
|
|
14
|
-
export type { EcoPageComponent, GetPageDependencies, GetPageDependenciesContext, PageDependenciesResult, } from '../eco/eco.types.js';
|
|
14
|
+
export type { EcoPageComponent, FileOwnedDependencyContribution, GetPageDependencies, GetPageDependenciesContext, PageDependenciesResult, } from '../eco/eco.types.js';
|
|
15
15
|
export type { ProcessedAsset } from '../services/assets/asset-processing-service/assets.types.js';
|
|
16
16
|
/**
|
|
17
17
|
* Runtime-agnostic incoming WebSocket frame.
|
|
@@ -184,6 +184,7 @@ export class ProjectWatcher {
|
|
|
184
184
|
await this.refreshRouterRoutesCallback();
|
|
185
185
|
}
|
|
186
186
|
if (plan.reloadBrowser) {
|
|
187
|
+
await this.notifyProcessors(filePath, event);
|
|
187
188
|
this.requestBrowserReload();
|
|
188
189
|
return;
|
|
189
190
|
}
|
|
@@ -364,7 +365,10 @@ export class ProjectWatcher {
|
|
|
364
365
|
processorPaths.add(this.appConfig.absolutePaths.publicDir);
|
|
365
366
|
}
|
|
366
367
|
for (const watchPath of this.appConfig.additionalWatchPaths) {
|
|
367
|
-
|
|
368
|
+
const resolvedWatchPath = path.isAbsolute(watchPath) || watchPath.includes('*')
|
|
369
|
+
? watchPath
|
|
370
|
+
: path.resolve(this.appConfig.rootDir, watchPath);
|
|
371
|
+
processorPaths.add(resolvedWatchPath);
|
|
368
372
|
}
|
|
369
373
|
const ignored = createProjectWatcherIgnorePredicate(this.appConfig.absolutePaths);
|
|
370
374
|
this.watcher = chokidar.watch(Array.from(processorPaths), {
|