@ecopages/core 0.2.0-beta.25 → 0.2.0-beta.27

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 (144) hide show
  1. package/package.json +58 -50
  2. package/src/adapters/bun/server-adapter.js +2 -2
  3. package/src/adapters/node/server-adapter.js +2 -2
  4. package/src/adapters/shared/hmr-entrypoint-registrar.d.ts +10 -0
  5. package/src/adapters/shared/hmr-entrypoint-registrar.js +34 -10
  6. package/src/adapters/shared/runtime-server-lifecycle.js +1 -1
  7. package/src/adapters/shared/server-static-builder.d.ts +2 -1
  8. package/src/adapters/shared/server-static-builder.js +16 -14
  9. package/src/adapters/shared/shared-hmr-manager.d.ts +11 -4
  10. package/src/adapters/shared/shared-hmr-manager.js +22 -3
  11. package/src/build/README.md +92 -46
  12. package/src/build/app-build-manifest-runtime.d.ts +28 -2
  13. package/src/build/app-build-manifest-runtime.js +26 -10
  14. package/src/build/{browser-runtime-plugin.d.ts → browser/browser-runtime-plugin.d.ts} +1 -1
  15. package/src/build/{browser-runtime-plugin.js → browser/browser-runtime-plugin.js} +1 -1
  16. package/src/build/{jsx-ownership-plugins.d.ts → browser/jsx-ownership-plugins.d.ts} +2 -2
  17. package/src/build/{jsx-ownership-plugins.js → browser/jsx-ownership-plugins.js} +2 -2
  18. package/src/build/build-adapter.d.ts +18 -21
  19. package/src/build/build-adapter.js +8 -43
  20. package/src/build/{build-input-fingerprint.d.ts → cache/build-input-fingerprint.d.ts} +3 -3
  21. package/src/build/cache/cache-constants.d.ts +4 -0
  22. package/src/build/cache/cache-constants.js +6 -0
  23. package/src/build/cache/cache-keys.d.ts +22 -0
  24. package/src/build/cache/cache-keys.js +63 -0
  25. package/src/build/{dev-browser-script-cache.d.ts → cache/dev-browser-script-cache.d.ts} +2 -2
  26. package/src/build/{dev-browser-script-cache.js → cache/dev-browser-script-cache.js} +4 -4
  27. package/src/build/{pages-unified-graph-build.d.ts → cache/pages-unified-graph-build.d.ts} +1 -1
  28. package/src/build/{pages-unified-graph-build.js → cache/pages-unified-graph-build.js} +18 -26
  29. package/src/build/{production-build-cache.d.ts → cache/production-build-cache.d.ts} +1 -1
  30. package/src/build/{production-build-cache.js → cache/production-build-cache.js} +2 -2
  31. package/src/build/{server-entry-build-cache.d.ts → cache/server-entry-build-cache.d.ts} +3 -4
  32. package/src/build/{server-entry-build-cache.js → cache/server-entry-build-cache.js} +16 -12
  33. package/src/build/{build-contracts.d.ts → contracts/build-contracts.d.ts} +1 -2
  34. package/src/build/contracts/build-manifest.d.ts +90 -0
  35. package/src/build/{build-manifest.js → contracts/build-manifest.js} +2 -2
  36. package/src/build/{rolldown-adapter-helpers.d.ts → rolldown/rolldown-adapter-helpers.d.ts} +20 -3
  37. package/src/build/{rolldown-adapter-helpers.js → rolldown/rolldown-adapter-helpers.js} +26 -6
  38. package/src/build/{rolldown-build-adapter.d.ts → rolldown/rolldown-build-adapter.d.ts} +1 -1
  39. package/src/build/{rolldown-plugin-bridge.d.ts → rolldown/rolldown-plugin-bridge.d.ts} +2 -2
  40. package/src/build/{rolldown-plugin-bridge.js → rolldown/rolldown-plugin-bridge.js} +1 -1
  41. package/src/build/{rolldown-source-transform-pass.d.ts → rolldown/rolldown-source-transform-pass.d.ts} +1 -1
  42. package/src/build/{rolldown-source-transform-pass.js → rolldown/rolldown-source-transform-pass.js} +4 -1
  43. package/src/build/{runtime-build-output-normalizer.js → rolldown/runtime-build-output-normalizer.js} +2 -2
  44. package/src/build/{build-profile-options.d.ts → runtime/build-profile-options.d.ts} +2 -2
  45. package/src/build/runtime/build-request-identity.d.ts +17 -0
  46. package/src/build/runtime/build-request-identity.js +54 -0
  47. package/src/build/runtime/build-request-policy.d.ts +59 -0
  48. package/src/build/runtime/build-request-policy.js +46 -0
  49. package/src/build/{build-runtime.d.ts → runtime/build-runtime.d.ts} +8 -4
  50. package/src/build/{build-runtime.js → runtime/build-runtime.js} +11 -11
  51. package/src/build/{deduping-build-executor.d.ts → runtime/deduping-build-executor.d.ts} +2 -9
  52. package/src/build/runtime/deduping-build-executor.js +34 -0
  53. package/src/build/{parallel-build-executor.d.ts → runtime/parallel-build-executor.d.ts} +1 -1
  54. package/src/build/{serialized-build-executor.d.ts → runtime/serialized-build-executor.d.ts} +1 -1
  55. package/src/config/config-builder.d.ts +1 -1
  56. package/src/diagnostics/startup-trace.d.ts +49 -2
  57. package/src/diagnostics/startup-trace.js +140 -8
  58. package/src/hmr/client/hmr-runtime.js +1 -1
  59. package/src/hmr/hmr-file-change-prep.d.ts +10 -0
  60. package/src/hmr/hmr-file-change-prep.js +17 -0
  61. package/src/plugins/alias-resolver-plugin.d.ts +1 -1
  62. package/src/plugins/eco-component-meta-plugin.d.ts +1 -1
  63. package/src/plugins/foreign-jsx-override-plugin.d.ts +1 -1
  64. package/src/plugins/integration-plugin.d.ts +16 -5
  65. package/src/plugins/integration-plugin.js +11 -1
  66. package/src/plugins/processor.d.ts +28 -8
  67. package/src/plugins/source-transform.d.ts +1 -1
  68. package/src/route-renderer/GRAPH.md +13 -19
  69. package/src/route-renderer/README.md +1 -1
  70. package/src/route-renderer/orchestration/component-render-context.d.ts +4 -4
  71. package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +6 -2
  72. package/src/route-renderer/orchestration/integration-renderer.d.ts +11 -52
  73. package/src/route-renderer/orchestration/integration-renderer.js +38 -91
  74. package/src/route-renderer/orchestration/integration-route-render-adapter.d.ts +1 -8
  75. package/src/route-renderer/orchestration/integration-route-render-adapter.js +0 -2
  76. package/src/route-renderer/orchestration/page-browser-graph-session.d.ts +106 -0
  77. package/src/route-renderer/orchestration/page-browser-graph-session.js +436 -0
  78. package/src/route-renderer/orchestration/page-browser-graph.service.d.ts +9 -3
  79. package/src/route-renderer/orchestration/page-browser-graph.service.js +89 -40
  80. package/src/route-renderer/orchestration/render-output.utils.d.ts +14 -0
  81. package/src/route-renderer/orchestration/render-output.utils.js +30 -0
  82. package/src/route-renderer/orchestration/route-html-finalization.service.d.ts +0 -1
  83. package/src/route-renderer/orchestration/route-html-finalization.service.js +4 -9
  84. package/src/route-renderer/orchestration/route-prepared-options.builder.d.ts +1 -2
  85. package/src/route-renderer/orchestration/route-prepared-options.builder.js +1 -10
  86. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +3 -12
  87. package/src/route-renderer/orchestration/route-render-orchestrator.js +4 -14
  88. package/src/services/assets/asset-processing-service/asset-dependency-keys.d.ts +13 -0
  89. package/src/services/assets/asset-processing-service/asset-dependency-keys.js +3 -0
  90. package/src/services/assets/asset-processing-service/asset-processing.service.d.ts +3 -1
  91. package/src/services/assets/asset-processing-service/asset-processing.service.js +4 -2
  92. package/src/services/assets/asset-processing-service/assets.types.d.ts +1 -1
  93. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.d.ts +1 -1
  94. package/src/services/assets/asset-processing-service/grouped-content-bundles.js +19 -3
  95. package/src/services/assets/asset-processing-service/inline-content-script-body.d.ts +11 -0
  96. package/src/services/assets/asset-processing-service/inline-content-script-body.js +17 -0
  97. package/src/services/assets/asset-processing-service/materialize-content-script-asset.d.ts +4 -6
  98. package/src/services/assets/asset-processing-service/materialize-content-script-asset.js +2 -1
  99. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.d.ts +0 -2
  100. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.js +2 -15
  101. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.d.ts +3 -2
  102. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +33 -54
  103. package/src/services/assets/browser-bundle.service.d.ts +27 -31
  104. package/src/services/assets/browser-bundle.service.js +12 -23
  105. package/src/services/module-loading/app-server-module-transpiler.service.d.ts +8 -0
  106. package/src/services/module-loading/app-server-module-transpiler.service.js +1 -7
  107. package/src/services/module-loading/page-module-import.service.d.ts +1 -1
  108. package/src/services/module-loading/page-module-import.service.js +40 -28
  109. package/src/services/module-loading/route-module-build-cache.store.js +1 -1
  110. package/src/services/module-loading/route-module-build-manifest.d.ts +2 -9
  111. package/src/services/module-loading/route-module-build-manifest.js +12 -43
  112. package/src/services/module-loading/server-module-transpiler.service.d.ts +1 -1
  113. package/src/static-site-generator/README.md +1 -1
  114. package/src/static-site-generator/production-page-browser-graph-prebuild.d.ts +28 -0
  115. package/src/static-site-generator/production-page-browser-graph-prebuild.js +22 -0
  116. package/src/static-site-generator/static-build-invalidation.d.ts +2 -2
  117. package/src/static-site-generator/static-build-invalidation.js +3 -3
  118. package/src/static-site-generator/static-site-generator.js +23 -1
  119. package/src/types/internal-types.d.ts +10 -4
  120. package/src/types/public-types.d.ts +58 -9
  121. package/src/watchers/project-watcher.js +12 -2
  122. package/src/build/build-manifest.d.ts +0 -33
  123. package/src/build/deduping-build-executor.js +0 -56
  124. package/src/build/runtime-build-executor.d.ts +0 -14
  125. package/src/build/runtime-build-executor.js +0 -16
  126. /package/src/build/{browser-runtime-manifest.d.ts → browser/browser-runtime-manifest.d.ts} +0 -0
  127. /package/src/build/{browser-runtime-manifest.js → browser/browser-runtime-manifest.js} +0 -0
  128. /package/src/build/{browser-runtime-plugin-helpers.d.ts → browser/browser-runtime-plugin-helpers.d.ts} +0 -0
  129. /package/src/build/{browser-runtime-plugin-helpers.js → browser/browser-runtime-plugin-helpers.js} +0 -0
  130. /package/src/build/{lit-static-render-worker-context.d.ts → browser/lit-static-render-worker-context.d.ts} +0 -0
  131. /package/src/build/{lit-static-render-worker-context.js → browser/lit-static-render-worker-context.js} +0 -0
  132. /package/src/build/{build-input-fingerprint.js → cache/build-input-fingerprint.js} +0 -0
  133. /package/src/build/{build-contracts.js → contracts/build-contracts.js} +0 -0
  134. /package/src/build/{build-types.d.ts → contracts/build-types.d.ts} +0 -0
  135. /package/src/build/{build-types.js → contracts/build-types.js} +0 -0
  136. /package/src/build/{rolldown-build-adapter.js → rolldown/rolldown-build-adapter.js} +0 -0
  137. /package/src/build/{rolldown-build-invocation-metrics.d.ts → rolldown/rolldown-build-invocation-metrics.d.ts} +0 -0
  138. /package/src/build/{rolldown-build-invocation-metrics.js → rolldown/rolldown-build-invocation-metrics.js} +0 -0
  139. /package/src/build/{runtime-build-output-normalizer.d.ts → rolldown/runtime-build-output-normalizer.d.ts} +0 -0
  140. /package/src/build/{server-side-css-shim-plugin.d.ts → rolldown/server-side-css-shim-plugin.d.ts} +0 -0
  141. /package/src/build/{server-side-css-shim-plugin.js → rolldown/server-side-css-shim-plugin.js} +0 -0
  142. /package/src/build/{build-profile-options.js → runtime/build-profile-options.js} +0 -0
  143. /package/src/build/{parallel-build-executor.js → runtime/parallel-build-executor.js} +0 -0
  144. /package/src/build/{serialized-build-executor.js → runtime/serialized-build-executor.js} +0 -0
@@ -3,6 +3,12 @@ import { fileSystem } from "@ecopages/file-system";
3
3
  import {
4
4
  } from "../../services/assets/asset-processing-service/index.js";
5
5
  import { appLogger } from "../../global/app-logger.js";
6
+ import { startupTrace } from "../../diagnostics/startup-trace.js";
7
+ import {
8
+ collectPageBrowserGraphDependencyPaths,
9
+ createPageBrowserGraphEntryFingerprint,
10
+ getAppPageBrowserGraphSession
11
+ } from "./page-browser-graph-session.js";
6
12
  function isGroupedContentScriptAsset(asset) {
7
13
  return asset.kind === "script" && asset.source === "content" && Boolean(asset.groupedBundle?.id);
8
14
  }
@@ -12,76 +18,106 @@ function getGroupedBundleAssetKey(groupedBundle) {
12
18
  class PageBrowserGraphService {
13
19
  appConfig;
14
20
  assetProcessingService;
15
- pageBrowserGraphCache = /* @__PURE__ */ new Map();
16
- groupedPageBrowserGraphCache = /* @__PURE__ */ new Map();
17
21
  constructor(appConfig, assetProcessingService) {
18
22
  this.appConfig = appConfig;
19
23
  this.assetProcessingService = assetProcessingService;
20
24
  }
21
25
  async resolvePageBrowserGraph(input) {
22
- if (this.isHmrEnabled()) {
23
- return await this.buildPageBrowserGraph(input);
26
+ return await this.getOrBuild(input);
27
+ }
28
+ /**
29
+ * Resolves or builds one Page Browser Graph under the given policy.
30
+ */
31
+ async getOrBuild(input) {
32
+ const policy = input.policy ?? this.resolveGraphPolicy();
33
+ const session = getAppPageBrowserGraphSession(this.appConfig);
34
+ if (!this.isHmrEnabled()) {
35
+ const cachedByRoute = session.getGraphByRoute(input.integrationName, input.routeFile, policy);
36
+ if (cachedByRoute) {
37
+ return cachedByRoute;
38
+ }
24
39
  }
25
- const cacheKey = `${input.integrationName}:${input.routeFile}`;
26
- const cachedGraph = this.pageBrowserGraphCache.get(cacheKey);
27
- if (cachedGraph) {
28
- return await cachedGraph;
40
+ const contribution = await input.collectContribution(input.routeFile);
41
+ if (!contribution) {
42
+ return void 0;
29
43
  }
30
- const pendingGraph = this.buildPageBrowserGraph(input).catch((error) => {
31
- this.pageBrowserGraphCache.delete(cacheKey);
32
- throw error;
33
- });
34
- this.pageBrowserGraphCache.set(cacheKey, pendingGraph);
35
- return await pendingGraph;
44
+ const entryFingerprint = createPageBrowserGraphEntryFingerprint(contribution);
45
+ return await session.resolveGraph(
46
+ {
47
+ integrationName: input.integrationName,
48
+ routeFile: input.routeFile,
49
+ entryFingerprint,
50
+ policy
51
+ },
52
+ async () => this.buildPageBrowserGraphRecord(input, contribution)
53
+ );
54
+ }
55
+ resolveGraphPolicy() {
56
+ return this.isHmrEnabled() ? "development" : "production";
36
57
  }
37
58
  isHmrEnabled() {
38
59
  return typeof this.assetProcessingService.getHmrManager === "function" && this.assetProcessingService.getHmrManager()?.isEnabled() === true;
39
60
  }
40
- async buildPageBrowserGraph(input) {
41
- const contribution = await input.collectContribution(input.routeFile);
42
- if (!contribution) {
61
+ async buildPageBrowserGraphRecord(input, contribution) {
62
+ const result = await this.materializePageBrowserGraph(input, contribution);
63
+ if (!result) {
43
64
  return void 0;
44
65
  }
66
+ startupTrace.markFirstPageBrowserGraphReady(getAppPageBrowserGraphSession(this.appConfig).getBuildCount());
67
+ return result;
68
+ }
69
+ async materializePageBrowserGraph(input, contribution) {
45
70
  const groupedDependencies = (contribution.dependencies ?? []).filter((dep) => isGroupedContentScriptAsset(dep));
46
71
  const ungroupedDependencies = (contribution.dependencies ?? []).filter(
47
72
  (dep) => !isGroupedContentScriptAsset(dep)
48
73
  );
49
- const groupedAssets = groupedDependencies.length ? (await this.resolveGroupedPageBrowserAssets(input, contribution)).get(input.routeFile) ?? [] : [];
74
+ const groupedAssets = groupedDependencies.length ? (await this.resolveGroupedPageBrowserAssets(input, contribution)).assetsByRoute.get(input.routeFile) ?? [] : [];
50
75
  const processedDependencies = ungroupedDependencies.length ? await this.assetProcessingService.processDependencies(
51
76
  ungroupedDependencies,
52
77
  `${input.integrationName}:${input.routeFile}`
53
78
  ) : [];
54
79
  const resolvedAssets = [...processedDependencies, ...groupedAssets, ...contribution.assets ?? []];
55
- return this.partitionPageBrowserGraphAssets(resolvedAssets);
80
+ const result = this.partitionPageBrowserGraphAssets(resolvedAssets);
81
+ const dependencyPaths = collectPageBrowserGraphDependencyPaths(input.routeFile, contribution, resolvedAssets);
82
+ return {
83
+ result,
84
+ dependencyPaths
85
+ };
56
86
  }
57
87
  async resolveGroupedPageBrowserAssets(input, currentContribution) {
58
88
  if (this.isHmrEnabled()) {
59
- const result = await this.buildGroupedPageBrowserAssets(input, currentContribution);
60
- return result.assetsByRoute;
89
+ return await this.buildGroupedPageBrowserAssets(input, currentContribution);
61
90
  }
62
- const cacheKey = input.integrationName;
63
- let pending = this.groupedPageBrowserGraphCache.get(cacheKey);
64
- if (!pending) {
65
- pending = this.buildGroupedPageBrowserAssets(input, currentContribution).then((result) => {
66
- if (result.hasCollectionFailures) {
67
- this.groupedPageBrowserGraphCache.delete(cacheKey);
68
- }
69
- return result.assetsByRoute;
70
- }).catch((error) => {
71
- this.groupedPageBrowserGraphCache.delete(cacheKey);
72
- throw error;
73
- });
74
- this.groupedPageBrowserGraphCache.set(cacheKey, pending);
75
- }
76
- return await pending;
91
+ const session = getAppPageBrowserGraphSession(this.appConfig);
92
+ const assetsByRoute = await session.resolveGroupedGraph(input.integrationName, async () => {
93
+ const built = await this.buildGroupedPageBrowserAssets(input, currentContribution);
94
+ if (built.hasCollectionFailures) {
95
+ return {
96
+ skipCache: true,
97
+ assetsByRoute: built.assetsByRoute,
98
+ dependencyPaths: built.dependencyPaths
99
+ };
100
+ }
101
+ return {
102
+ assetsByRoute: built.assetsByRoute,
103
+ dependencyPaths: built.dependencyPaths,
104
+ generation: 0
105
+ };
106
+ });
107
+ return {
108
+ assetsByRoute,
109
+ dependencyPaths: /* @__PURE__ */ new Set(),
110
+ hasCollectionFailures: false
111
+ };
77
112
  }
78
113
  async buildGroupedPageBrowserAssets(input, currentContribution) {
79
- const routeFiles = await this.listIntegrationRouteFiles(input.integrationName);
114
+ const routeFiles = this.isHmrEnabled() ? [input.routeFile] : await this.listIntegrationRouteFiles(input.integrationName);
80
115
  const currentRouteGroupedDependencies = (currentContribution.dependencies ?? []).filter(
81
116
  (dep) => isGroupedContentScriptAsset(dep)
82
117
  );
83
118
  const groupedDependencies = [...currentRouteGroupedDependencies];
84
119
  const groupedAssetKeysByRoute = /* @__PURE__ */ new Map();
120
+ const dependencyPaths = /* @__PURE__ */ new Set([path.resolve(input.routeFile)]);
85
121
  let hasCollectionFailures = false;
86
122
  if (currentRouteGroupedDependencies.length > 0) {
87
123
  groupedAssetKeysByRoute.set(
@@ -103,9 +139,6 @@ class PageBrowserGraphService {
103
139
  );
104
140
  continue;
105
141
  }
106
- if (this.isHmrEnabled()) {
107
- continue;
108
- }
109
142
  if (!contribution?.dependencies?.length) {
110
143
  continue;
111
144
  }
@@ -120,13 +153,20 @@ class PageBrowserGraphService {
120
153
  routeFile,
121
154
  new Set(routeGroupedDependencies.map((dep) => getGroupedBundleAssetKey(dep.groupedBundle)))
122
155
  );
156
+ dependencyPaths.add(path.resolve(routeFile));
123
157
  }
124
158
  if (groupedDependencies.length === 0) {
125
159
  return {
126
160
  assetsByRoute: /* @__PURE__ */ new Map(),
161
+ dependencyPaths,
127
162
  hasCollectionFailures
128
163
  };
129
164
  }
165
+ for (const dependency of groupedDependencies) {
166
+ if (dependency.source === "file") {
167
+ dependencyPaths.add(path.resolve(dependency.filepath));
168
+ }
169
+ }
130
170
  const processedGroupedDependencies = await this.assetProcessingService.processDependencies(
131
171
  groupedDependencies,
132
172
  `${input.integrationName}:grouped-page-browser-graph`
@@ -146,8 +186,17 @@ class PageBrowserGraphService {
146
186
  }
147
187
  groupedAssetsByRoute.set(routeFile, matchedAssets);
148
188
  }
189
+ for (const asset of processedGroupedDependencies) {
190
+ if (asset.sourceFilepath) {
191
+ dependencyPaths.add(path.resolve(asset.sourceFilepath));
192
+ }
193
+ for (const bundledSourceFilepath of asset.bundledSourceFilepaths ?? []) {
194
+ dependencyPaths.add(path.resolve(bundledSourceFilepath));
195
+ }
196
+ }
149
197
  return {
150
198
  assetsByRoute: groupedAssetsByRoute,
199
+ dependencyPaths,
151
200
  hasCollectionFailures
152
201
  };
153
202
  }
@@ -23,6 +23,20 @@ type MarkupNodeLikeShape = {
23
23
  export declare function isThenable<T>(value: unknown): value is PromiseLike<T>;
24
24
  /** Returns whether a value looks like a DOM-like markup node from SSR integrations. */
25
25
  export declare function isMarkupNodeLike(value: unknown): value is MarkupNodeLikeShape;
26
+ /**
27
+ * Returns whether a value is a plain opaque object that string serializers would
28
+ * coerce via `String(value)` rather than render as markup.
29
+ *
30
+ * @remarks
31
+ * Template results (`strings`/`values`), markup nodes, arrays, and framework
32
+ * element markers (`$$typeof`) are not opaque. Cross-integration children that
33
+ * are only plain objects must use EcoEmbed or already-serialized HTML.
34
+ */
35
+ export declare function isOpaqueForeignChildValue(children: unknown): boolean;
36
+ /**
37
+ * Throws when foreign-path children would be coerced from an opaque object.
38
+ */
39
+ export declare function assertForeignChildrenNotOpaque(children: unknown, context: string): void;
26
40
  /**
27
41
  * Injects `data-eco-trigger` into the first real HTML element opening tag of
28
42
  * a component's rendered output string.
@@ -85,6 +85,34 @@ function isMarkupNodeLikeShape(value) {
85
85
  function isMarkupNodeLike(value) {
86
86
  return isMarkupNodeLikeShape(value);
87
87
  }
88
+ function isOpaqueForeignChildValue(children) {
89
+ if (children === null || typeof children !== "object") {
90
+ return false;
91
+ }
92
+ if (Array.isArray(children)) {
93
+ return false;
94
+ }
95
+ if ("$$typeof" in children) {
96
+ return false;
97
+ }
98
+ if ("strings" in children && Array.isArray(children.strings)) {
99
+ return false;
100
+ }
101
+ if (isMarkupNodeLikeShape(children)) {
102
+ return false;
103
+ }
104
+ const prototype = Object.getPrototypeOf(children);
105
+ return prototype === Object.prototype || prototype === null;
106
+ }
107
+ function assertForeignChildrenNotOpaque(children, context) {
108
+ if (!isOpaqueForeignChildValue(children)) {
109
+ return;
110
+ }
111
+ const childTag = Object.prototype.toString.call(children);
112
+ throw new TypeError(
113
+ `[ecopages] ${context} refused to coerce opaque foreign children (${childTag}). Use EcoEmbed for cross-integration children or pass already-serialized HTML.`
114
+ );
115
+ }
88
116
  function injectTriggerAttributeIntoString(content, triggerId) {
89
117
  const str = content;
90
118
  let i = 0;
@@ -166,9 +194,11 @@ function inspectUnresolvedMarkerArtifactHtml(html) {
166
194
  }
167
195
  export {
168
196
  addTriggerAttribute,
197
+ assertForeignChildrenNotOpaque,
169
198
  decodeHtmlEntities,
170
199
  inspectUnresolvedMarkerArtifactHtml,
171
200
  isMarkupNodeLike,
201
+ isOpaqueForeignChildValue,
172
202
  isThenable,
173
203
  normalizeUnresolvedMarkerArtifactHtml,
174
204
  wrapWithScriptsInjector
@@ -8,7 +8,6 @@ export type RouteHtmlFinalizationContext<C> = {
8
8
  renderOptions: IntegrationRendererRenderOptions<C>;
9
9
  partial: boolean;
10
10
  }) => HtmlDocumentContribution[] | undefined;
11
- applyAttributesToFirstBodyElement: (html: string, attributes: Record<string, string>) => string;
12
11
  applyAttributesToHtmlElement: (html: string, attributes: Record<string, string>) => string;
13
12
  };
14
13
  /**
@@ -1,23 +1,18 @@
1
1
  function buildRouteHtmlFinalization(context) {
2
2
  const { renderOptions } = context;
3
- const componentRootAttributes = renderOptions.componentRender?.canAttachAttributes && renderOptions.componentRender.rootAttributes && Object.keys(renderOptions.componentRender.rootAttributes).length > 0 ? renderOptions.componentRender.rootAttributes : void 0;
4
3
  const documentAttributes = context.getDocumentAttributes(renderOptions);
5
4
  const htmlContributions = context.getHtmlDocumentContributions({ renderOptions, partial: false });
6
- const hasStructuralFinalization = componentRootAttributes && Object.keys(componentRootAttributes).length > 0 || documentAttributes && Object.keys(documentAttributes).length > 0;
5
+ const hasStructuralFinalization = documentAttributes && Object.keys(documentAttributes).length > 0;
7
6
  if (!hasStructuralFinalization && (!htmlContributions || htmlContributions.length === 0)) {
8
7
  return {};
9
8
  }
10
9
  return {
11
10
  htmlContributions,
12
11
  finalizeHtml: (html) => {
13
- let renderedHtml = html;
14
- if (componentRootAttributes) {
15
- renderedHtml = context.applyAttributesToFirstBodyElement(renderedHtml, componentRootAttributes);
12
+ if (!documentAttributes || Object.keys(documentAttributes).length === 0) {
13
+ return html;
16
14
  }
17
- if (documentAttributes) {
18
- renderedHtml = context.applyAttributesToHtmlElement(renderedHtml, documentAttributes);
19
- }
20
- return renderedHtml;
15
+ return context.applyAttributesToHtmlElement(html, documentAttributes);
21
16
  }
22
17
  };
23
18
  }
@@ -1,5 +1,5 @@
1
1
  import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
- import type { ComponentRenderResult, EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphResult, PageMetadataProps, EcoPageLayoutEntry, RouteRendererOptions } from '../../types/public-types.js';
2
+ import type { EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphResult, PageMetadataProps, EcoPageLayoutEntry, RouteRendererOptions } from '../../types/public-types.js';
3
3
  import { type ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
4
4
  type PreparedRenderInputs = {
5
5
  Page: EcoPageFile['default'] | EcoPageComponent<any>;
@@ -20,7 +20,6 @@ export declare function buildPreparedRenderOptions<C = unknown>(input: {
20
20
  resolvedDependencies: ProcessedAsset[];
21
21
  allDependencies: ProcessedAsset[];
22
22
  pageBrowserGraph?: PageBrowserGraphResult;
23
- componentRender?: ComponentRenderResult;
24
23
  appConfig: EcoPagesAppConfig;
25
24
  }): IntegrationRendererRenderOptions<C>;
26
25
  export {};
@@ -2,15 +2,7 @@ import { createPagePackage } from "../../services/assets/asset-processing-servic
2
2
  import { dedupeProcessedAssets } from "./processed-asset-dedupe.js";
3
3
  import { createPageLocalsProxy } from "./route-prepared-options.utils.js";
4
4
  function buildPreparedRenderOptions(input) {
5
- const {
6
- routeOptions,
7
- resolvedInputs,
8
- resolvedDependencies,
9
- allDependencies,
10
- pageBrowserGraph,
11
- componentRender,
12
- appConfig
13
- } = input;
5
+ const { routeOptions, resolvedInputs, resolvedDependencies, allDependencies, pageBrowserGraph, appConfig } = input;
14
6
  const { Page, HtmlTemplate, Layouts, Layout, layoutEntries, props, metadata, integrationSpecificProps } = resolvedInputs;
15
7
  const dedupedDependencies = dedupeProcessedAssets(allDependencies);
16
8
  const pagePackage = createPagePackage(dedupedDependencies, { pageBrowserGraph });
@@ -29,7 +21,6 @@ function buildPreparedRenderOptions(input) {
29
21
  ...routeOptions,
30
22
  resolvedDependencies,
31
23
  pagePackage,
32
- componentRender,
33
24
  HtmlTemplate,
34
25
  Layouts,
35
26
  Layout,
@@ -1,5 +1,5 @@
1
1
  import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
- import type { ComponentRenderResult, EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult, EcoPageLayoutEntry } from '../../types/public-types.js';
2
+ import type { EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult, EcoPageLayoutEntry } from '../../types/public-types.js';
3
3
  import type { AssetProcessingService, ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
4
4
  import type { HtmlDocumentContribution } from '../../services/html/html-transformer.service.js';
5
5
  import { OwnershipValidationService } from './ownership-validation.service.js';
@@ -46,15 +46,6 @@ export interface RouteRenderOrchestratorAdapter<C> {
46
46
  * Collects declarative Page Browser Graph requirements for one route.
47
47
  */
48
48
  collectPageBrowserGraphContribution(routeFile: string): Promise<PageBrowserGraphContribution | undefined>;
49
- /**
50
- * Resolves the optional page-root render through the foreign-child-aware component contract.
51
- */
52
- resolveRoutePageComponentRender(input: {
53
- Page: EcoComponent;
54
- Layout?: EcoComponent;
55
- props: Record<string, unknown>;
56
- routeOptions: RouteRendererOptions;
57
- }): Promise<ComponentRenderResult | undefined>;
58
49
  /**
59
50
  * Executes the Integration-specific route render.
60
51
  */
@@ -100,8 +91,8 @@ export declare class RouteRenderOrchestrator {
100
91
  * Builds normalized route render options before the integration render runs.
101
92
  *
102
93
  * This preparation step validates route-root ownership, resolves page data,
103
- * collects processed assets, captures optional page-root render metadata, and
104
- * produces the page package consumed by downstream HTML transformation.
94
+ * collects processed assets, and produces the page package consumed by downstream
95
+ * HTML transformation.
105
96
  */
106
97
  prepareRenderOptions<C = unknown>(routeOptions: RouteRendererOptions, adapter: RouteRenderOrchestratorAdapter<C>): Promise<IntegrationRendererRenderOptions<C>>;
107
98
  resolveDeclaredPageBrowserGraph(input: {
@@ -23,12 +23,12 @@ class RouteRenderOrchestrator {
23
23
  * Builds normalized route render options before the integration render runs.
24
24
  *
25
25
  * This preparation step validates route-root ownership, resolves page data,
26
- * collects processed assets, captures optional page-root render metadata, and
27
- * produces the page package consumed by downstream HTML transformation.
26
+ * collects processed assets, and produces the page package consumed by downstream
27
+ * HTML transformation.
28
28
  */
29
29
  async prepareRenderOptions(routeOptions, adapter) {
30
30
  const resolvedInputs = await adapter.resolveRouteRenderInputs(routeOptions);
31
- const { Page, HtmlTemplate, Layouts, Layout } = resolvedInputs;
31
+ const { Page, HtmlTemplate, Layouts } = resolvedInputs;
32
32
  const validationErrors = this.ownershipValidationService.validate({
33
33
  currentIntegrationName: adapter.name,
34
34
  roots: [
@@ -39,7 +39,7 @@ class RouteRenderOrchestrator {
39
39
  });
40
40
  throwIfOwnershipInvalid(validationErrors);
41
41
  const componentsToResolve = [HtmlTemplate, ...Layouts, Page];
42
- const [{ resolvedDependencies }, pageBrowserGraph, componentRender] = await Promise.all([
42
+ const [{ resolvedDependencies }, pageBrowserGraph] = await Promise.all([
43
43
  adapter.resolveRouteDependencies({
44
44
  components: componentsToResolve
45
45
  }),
@@ -47,21 +47,12 @@ class RouteRenderOrchestrator {
47
47
  routeFile: routeOptions.file,
48
48
  integrationName: adapter.name,
49
49
  collectContribution: async (routeFile) => await adapter.collectPageBrowserGraphContribution(routeFile)
50
- }),
51
- adapter.resolveRoutePageComponentRender({
52
- Page,
53
- Layout,
54
- props: resolvedInputs.props,
55
- routeOptions
56
50
  })
57
51
  ]);
58
52
  const allDependencies = [
59
53
  ...resolvedDependencies,
60
54
  ...collectUsedIntegrationDependenciesFromGraph(this.appConfig, componentsToResolve, adapter.name)
61
55
  ];
62
- if (componentRender?.assets?.length) {
63
- allDependencies.push(...componentRender.assets);
64
- }
65
56
  const triggers = collectResolvedLazyTriggersFromGraph(componentsToResolve, adapter.name);
66
57
  const [globalAssets, eagerSsrLazyAssets] = await Promise.all([
67
58
  triggers.length > 0 ? buildGlobalInjectorAssets(this.appConfig, this.assetProcessingService, triggers, adapter.name) : Promise.resolve([]),
@@ -74,7 +65,6 @@ class RouteRenderOrchestrator {
74
65
  resolvedDependencies,
75
66
  allDependencies,
76
67
  pageBrowserGraph,
77
- componentRender,
78
68
  appConfig: this.appConfig
79
69
  });
80
70
  }
@@ -1,3 +1,16 @@
1
1
  import type { AssetDefinition } from './assets.types.js';
2
+ /**
3
+ * Stable identity for dependency deduplication and {@link AssetProcessingService} cache lookups.
4
+ *
5
+ * @remarks
6
+ * Script dependencies include a build signature derived from bundle flags, grouped-bundle
7
+ * metadata, and selected `bundleOptions` fields (`naming`, `external`, `minify`, plugin names).
8
+ * HTML-affecting script `attributes` are hashed into the key so dedupe and cache reuse do not
9
+ * collapse declarations that would emit different `<script>` tags. Full plugin objects and
10
+ * `NODE_ENV` are excluded from the key.
11
+ *
12
+ * Runtime minify in {@link ContentScriptProcessor} still follows `NODE_ENV` via
13
+ * `isProduction`, not `bundleOptions.minify`.
14
+ */
2
15
  export declare function getAssetDependencyKey(dep: AssetDefinition): string;
3
16
  export declare function deduplicateAssetDependencies(deps: AssetDefinition[]): AssetDefinition[];
@@ -34,6 +34,9 @@ function getAssetDependencyKey(dep) {
34
34
  if ("packageRole" in dep && dep.packageRole) {
35
35
  parts.push(`package:${dep.packageRole}`);
36
36
  }
37
+ if (dep.kind === "script" && dep.attributes) {
38
+ parts.push(`attrs:${generateHash(JSON.stringify(dep.attributes))}`);
39
+ }
37
40
  const scriptBuildSignature = getScriptDependencyBuildSignature(dep);
38
41
  if (scriptBuildSignature) {
39
42
  parts.push(`build:${scriptBuildSignature}`);
@@ -39,7 +39,9 @@ export declare class AssetProcessingService {
39
39
  * @remarks
40
40
  * Dependencies are deduplicated before processor execution so repeated
41
41
  * declarations across the render tree reuse the same emitted outputs and cache
42
- * entries.
42
+ * entries. Returned asset order is unspecified — consumers must re-associate
43
+ * outputs with inputs via dependency metadata such as `groupedBundle`, not by
44
+ * array index alignment with the input list.
43
45
  */
44
46
  processDependencies(deps: AssetDefinition[], key: string): Promise<ProcessedAsset[]>;
45
47
  private prepareDependenciesForProcessing;
@@ -16,7 +16,7 @@ import { materializeContentScriptAsset } from "./materialize-content-script-asse
16
16
  import {
17
17
  getDevBrowserScriptCacheEntry,
18
18
  setDevBrowserScriptCacheEntry
19
- } from "../../../build/dev-browser-script-cache.js";
19
+ } from "../../../build/cache/dev-browser-script-cache.js";
20
20
  import {
21
21
  ContentScriptProcessor,
22
22
  ContentStylesheetProcessor,
@@ -69,7 +69,9 @@ class AssetProcessingService {
69
69
  * @remarks
70
70
  * Dependencies are deduplicated before processor execution so repeated
71
71
  * declarations across the render tree reuse the same emitted outputs and cache
72
- * entries.
72
+ * entries. Returned asset order is unspecified — consumers must re-associate
73
+ * outputs with inputs via dependency metadata such as `groupedBundle`, not by
74
+ * array index alignment with the input list.
73
75
  */
74
76
  async processDependencies(deps, key) {
75
77
  const depsDir = path.join(this.config.absolutePaths.distDir, RESOLVED_ASSETS_DIR);
@@ -1,4 +1,4 @@
1
- import type { EcoBuildPlugin } from '../../../build/build-types.js';
1
+ import type { EcoBuildPlugin } from '../../../build/contracts/build-types.js';
2
2
  export type AssetSource = 'content' | 'file' | 'node-module';
3
3
  export type AssetKind = 'script' | 'stylesheet';
4
4
  export type AssetPosition = 'head' | 'body';
@@ -1,4 +1,4 @@
1
- import type { EcoBuildPlugin } from '../../../build/build-types.js';
1
+ import type { EcoBuildPlugin } from '../../../build/contracts/build-types.js';
2
2
  import type { AssetDefinition } from './assets.types.js';
3
3
  import { type BrowserRuntimeEntryModuleConfig } from './browser-runtime-entry.factory.js';
4
4
  export declare const BROWSER_RUNTIME_SCRIPT_ATTRIBUTES: {
@@ -1,5 +1,8 @@
1
1
  import { isDevelopmentRuntime } from "../../../utils/runtime.js";
2
2
  import { finalizeProcessedAsset } from "./finalize-processed-asset.js";
3
+ function getGroupedBundleAssetKey(groupedBundle) {
4
+ return `${groupedBundle.id}:${groupedBundle.entryName}`;
5
+ }
3
6
  function ensureGroupedContentScriptsBundle(dependencies) {
4
7
  if (isDevelopmentRuntime()) {
5
8
  return;
@@ -54,12 +57,25 @@ async function processGroupedDependencyBundles(options) {
54
57
  }
55
58
  try {
56
59
  const processedResults = await processor.processGrouped(bundleDeps);
57
- return processedResults.map((processed, index) => {
58
- const dep = bundleDeps[index];
60
+ const processedByEntryKey = /* @__PURE__ */ new Map();
61
+ for (const processed of processedResults) {
62
+ if (!processed.groupedBundle) {
63
+ continue;
64
+ }
65
+ processedByEntryKey.set(getGroupedBundleAssetKey(processed.groupedBundle), processed);
66
+ }
67
+ return bundleDeps.flatMap((dep) => {
68
+ if (dep.kind !== "script" || dep.source !== "content" || !dep.groupedBundle) {
69
+ return [];
70
+ }
71
+ const processed = processedByEntryKey.get(getGroupedBundleAssetKey(dep.groupedBundle));
72
+ if (!processed) {
73
+ return [];
74
+ }
59
75
  const depKey = getDependencyKey(dep);
60
76
  const finalized = finalizeProcessedAsset(processed, resolveProcessedAssetSrcUrl);
61
77
  setCachedAsset(dep, depKey, finalized);
62
- return finalized;
78
+ return [finalized];
63
79
  });
64
80
  } catch (error) {
65
81
  logError(error);
@@ -0,0 +1,11 @@
1
+ import type { ContentScriptAsset } from './assets.types.js';
2
+ /**
3
+ * Resolves the script body to embed when a content script is marked inline.
4
+ *
5
+ * @remarks
6
+ * `inline` means embed in HTML, not "use declaration source." When bundling has
7
+ * already run, only the output file is valid browser script; `dep.content` may
8
+ * still contain build-time import paths. Processor emission and cache
9
+ * materialization must share this rule so they cannot drift.
10
+ */
11
+ export declare function resolveInlineContentScriptBody(dep: Pick<ContentScriptAsset, 'inline' | 'bundle' | 'content'>, filepath: string): string | undefined;
@@ -0,0 +1,17 @@
1
+ import { fileSystem } from "@ecopages/file-system";
2
+ import { appLogger } from "../../../global/app-logger.js";
3
+ function resolveInlineContentScriptBody(dep, filepath) {
4
+ if (!dep.inline) {
5
+ return void 0;
6
+ }
7
+ if (dep.bundle !== false && fileSystem.exists(filepath)) {
8
+ return fileSystem.readFileSync(filepath);
9
+ }
10
+ if (dep.bundle !== false) {
11
+ appLogger.warn(`Missing bundled inline script output at ${filepath}; falling back to declaration source.`);
12
+ }
13
+ return dep.content;
14
+ }
15
+ export {
16
+ resolveInlineContentScriptBody
17
+ };
@@ -1,11 +1,9 @@
1
1
  import type { ContentScriptAsset, ProcessedAsset } from './assets.types.js';
2
2
  /**
3
- * Builds a processed content-script asset from one dependency declaration and a
4
- * previously emitted output file.
5
- *
6
3
  * @remarks
7
- * Dev disk cache entries store only output paths. Callers must materialize the
8
- * full processed asset from the originating dependency so HTML and page-browser
9
- * graph assembly receive grouped-bundle metadata and script attributes.
4
+ * {@link AssetProcessingService} cache and dev disk cache entries usually keep
5
+ * only an output path. Dependency declarations still carry the metadata HTML and
6
+ * page-browser graph assembly need (`groupedBundle`, attributes, roles), so cache
7
+ * hits must be rehydrated from both sources rather than returned verbatim.
10
8
  */
11
9
  export declare function materializeContentScriptAsset(dep: ContentScriptAsset, filepath: string): ProcessedAsset;
@@ -1,9 +1,10 @@
1
+ import { resolveInlineContentScriptBody } from "./inline-content-script-body.js";
1
2
  function materializeContentScriptAsset(dep, filepath) {
2
3
  return {
3
4
  filepath,
4
5
  kind: "script",
5
6
  inline: dep.inline ?? false,
6
- content: dep.inline ? dep.content : void 0,
7
+ content: resolveInlineContentScriptBody(dep, filepath),
7
8
  position: dep.position,
8
9
  attributes: dep.attributes,
9
10
  excludeFromHtml: dep.excludeFromHtml,
@@ -1,5 +1,4 @@
1
1
  import type { EcoPagesAppConfig } from '../../../../../types/internal-types.js';
2
- import type { EcoBuildPlugin } from '../../../../../build/build-types.js';
3
2
  import type { ScriptAsset } from '../../assets.types.js';
4
3
  import { BaseProcessor } from './base-processor.js';
5
4
  import { type BrowserBundleGroupedEntry } from '../../../browser-bundle.service.js';
@@ -10,7 +9,6 @@ export declare abstract class BaseScriptProcessor<T extends ScriptAsset> extends
10
9
  });
11
10
  protected shouldBundle(dep: T): boolean;
12
11
  protected getBundlerOptions(dep: T): Record<string, any>;
13
- protected collectBuildPlugins(excludePluginNames?: string[]): EcoBuildPlugin[];
14
12
  protected bundleScript({ entrypoint, outdir, excludeAppBuildPlugins, plugins: additionalPlugins, ...rest }: {
15
13
  entrypoint: string;
16
14
  outdir: string;