@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.
Files changed (48) hide show
  1. package/README.md +5 -2
  2. package/package.json +7 -4
  3. package/src/build/contracts/content-virtual-modules.d.ts +3 -1
  4. package/src/build/contracts/content-virtual-modules.js +8 -0
  5. package/src/css-imports.d.ts +4 -0
  6. package/src/declarations.d.ts +2 -5
  7. package/src/dev/README.md +2 -0
  8. package/src/dev/transform-server/dev-transform-server.d.ts +6 -0
  9. package/src/dev/transform-server/dev-transform-server.js +16 -5
  10. package/src/eco/README.md +71 -5
  11. package/src/eco/component-identity.d.ts +3 -2
  12. package/src/eco/component-identity.js +5 -2
  13. package/src/eco/discovered-dependencies.d.ts +45 -0
  14. package/src/eco/discovered-dependencies.js +87 -0
  15. package/src/eco/eco.js +2 -0
  16. package/src/eco/eco.types.d.ts +18 -2
  17. package/src/eco/page-dependency-contributions.d.ts +19 -0
  18. package/src/eco/page-dependency-contributions.js +52 -0
  19. package/src/index.browser.d.ts +3 -0
  20. package/src/index.browser.js +3 -0
  21. package/src/index.d.ts +4 -1
  22. package/src/index.js +4 -1
  23. package/src/plugins/README.md +7 -1
  24. package/src/plugins/component-import-discovery.d.ts +19 -0
  25. package/src/plugins/component-import-discovery.js +203 -0
  26. package/src/plugins/eco-component-meta-plugin.d.ts +3 -1
  27. package/src/plugins/eco-component-meta-plugin.js +101 -16
  28. package/src/plugins/tsconfig-import-resolver.d.ts +3 -1
  29. package/src/plugins/tsconfig-import-resolver.js +4 -2
  30. package/src/route-renderer/GRAPH.md +1 -1
  31. package/src/route-renderer/README.md +8 -0
  32. package/src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.d.ts +8 -0
  33. package/src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.js +9 -1
  34. package/src/route-renderer/orchestration/integration-renderer.d.ts +2 -2
  35. package/src/route-renderer/orchestration/integration-renderer.js +8 -9
  36. package/src/route-renderer/orchestration/ownership-graph/component-graph-collectors.d.ts +1 -4
  37. package/src/route-renderer/orchestration/ownership-graph/component-graph-collectors.js +0 -66
  38. package/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.js +5 -8
  39. package/src/route-renderer/page-loading/component-dependency-collection.d.ts +6 -2
  40. package/src/route-renderer/page-loading/component-dependency-collection.js +29 -5
  41. package/src/route-renderer/page-loading/file-scoped-dependency-components.d.ts +11 -1
  42. package/src/route-renderer/page-loading/file-scoped-dependency-components.js +40 -6
  43. package/src/route-renderer/page-loading/resolved-page-dependencies.d.ts +1 -2
  44. package/src/route-renderer/page-loading/resolved-page-dependencies.js +8 -7
  45. package/src/services/module-loading/page-module-import.service.d.ts +2 -0
  46. package/src/services/module-loading/page-module-import.service.js +2 -1
  47. package/src/static-site-generator/README.md +3 -1
  48. package/src/types/public-types.d.ts +1 -1
@@ -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 { AssetDefinition } from '../../../services/assets/asset-processing-service/index.js';
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 { buildEagerSsrLazyAssetsFromGraph, collectResolvedLazyTriggersFromGraph, collectUsedIntegrationDependenciesFromGraph, } from '../ownership-graph/component-graph-collectors.js';
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 [globalAssets, eagerSsrLazyAssets] = await Promise.all([
97
- triggers.length > 0
98
- ? buildGlobalInjectorAssets(this.appConfig, this.assetProcessingService, triggers, adapter.name)
99
- : Promise.resolve([]),
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
- * The returned structures intentionally preserve both the flat asset list used by
34
- * asset processing and the grouped lazy-trigger data used later during render output.
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
- * The returned structures intentionally preserve both the flat asset list used by
37
- * asset processing and the grouped lazy-trigger data used later during render output.
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: dependenciesConfig?.stylesheets ?? [],
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 every resolved identity file path.
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
- * Walks component configs and collects every resolved identity file path.
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(resolved)) {
80
+ if (visited.has(config)) {
64
81
  return;
65
82
  }
66
- visited.add(resolved);
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 { dependencies, ownerFile };
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 = (dependencies: Parameters<typeof collectFileScopedDependencyComponents>[0]['dependencies'], ownerFile: string, components: ReadonlyArray<EcoComponent | Partial<EcoComponent>>) => Promise<PageBrowserGraphContribution | undefined>;
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 { collectFileScopedDependencyComponents, splitPageDependenciesResult, } from './file-scoped-dependency-components.js';
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 { dependencies, ownerFile } = splitPageDependenciesResult(dependenciesResult);
20
- const resolvedOwnerFile = ownerFile ?? context.file;
21
- const components = collectFileScopedDependencyComponents({
22
- ownerFile: resolvedOwnerFile,
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(dependencies, resolvedOwnerFile, components);
33
+ const contribution = await materializeContribution(components, resolvedOwnerFile);
33
34
  return {
34
35
  ownerFile: resolvedOwnerFile,
35
36
  components,
@@ -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 = typeof Bun === 'undefined' &&
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.